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.util.Map;
20  import java.util.concurrent.ConcurrentHashMap;
21  import java.util.concurrent.ConcurrentMap;
22  import java.util.concurrent.locks.Lock;
23  import java.util.concurrent.locks.ReadWriteLock;
24  import java.util.concurrent.locks.ReentrantReadWriteLock;
25  
26  import org.apache.commons.collections4.map.AbstractLinkedMap;
27  import org.apache.commons.collections4.map.LRUMap;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.commons.vfs2.FileName;
31  import org.apache.commons.vfs2.FileObject;
32  import org.apache.commons.vfs2.FileSystem;
33  import org.apache.commons.vfs2.FileSystemException;
34  import org.apache.commons.vfs2.VfsLog;
35  import org.apache.commons.vfs2.util.Messages;
36  
37  /**
38   * This implementation caches every file using {@link LRUMap}.
39   * <p>
40   * The default constructor uses a LRU size of 100 per file system.
41   * </p>
42   */
43  public class LRUFilesCache extends AbstractFilesCache {
44  
45      /** The default LRU size */
46      private static final int DEFAULT_LRU_SIZE = 100;
47  
48      /** The logger to use. */
49      private static final Log log = LogFactory.getLog(LRUFilesCache.class);
50  
51      /** The FileSystem cache */
52      private final ConcurrentMap<FileSystem, Map<FileName, FileObject>> filesystemCache = new ConcurrentHashMap<>(10);
53  
54      /** The size of the cache */
55      private final int lruSize;
56  
57      private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
58      private final Lock readLock = rwLock.readLock();
59      private final Lock writeLock = rwLock.writeLock();
60  
61      /**
62       * The file cache
63       */
64      private class MyLRUMap extends LRUMap<FileName, FileObject> {
65          /**
66           * serialVersionUID format is YYYYMMDD for the date of the last binary change.
67           */
68          private static final long serialVersionUID = 20101208L;
69  
70          /** The FileSystem */
71          private final FileSystem filesystem;
72  
73          public MyLRUMap(final FileSystem filesystem, final int size) {
74              super(size, true);
75              this.filesystem = filesystem;
76          }
77  
78          @Override
79          protected boolean removeLRU(final AbstractLinkedMap.LinkEntry<FileName, FileObject> linkEntry) {
80              synchronized (LRUFilesCache.this) {
81                  final FileObject file = linkEntry.getValue();
82  
83                  // System.err.println(">>> " + size() + " check removeLRU:" + linkEntry.getKey().toString());
84  
85                  if (file.isAttached() || file.isContentOpen()) {
86                      // do not allow open or attached files to be removed
87                      // System.err.println(">>> " + size() + " VETO removeLRU:" +
88                      // linkEntry.getKey().toString() + " (" + file.isAttached() + "/" +
89                      // file.isContentOpen() + ")");
90                      return false;
91                  }
92  
93                  // System.err.println(">>> " + size() + " removeLRU:" + linkEntry.getKey().toString());
94                  if (super.removeLRU(linkEntry)) {
95                      try {
96                          // force detach
97                          file.close();
98                      } catch (final FileSystemException e) {
99                          VfsLog.warn(getLogger(), log, Messages.getString("vfs.impl/LRUFilesCache-remove-ex.warn"), e);
100                     }
101 
102                     final Map<?, ?> files = filesystemCache.get(filesystem);
103                     if (files.size() < 1) {
104                         filesystemCache.remove(filesystem);
105                     }
106 
107                     return true;
108                 }
109 
110                 return false;
111             }
112         }
113     }
114 
115     /**
116      * Default constructor. Uses a LRU size of 100 per file system.
117      */
118     public LRUFilesCache() {
119         this(DEFAULT_LRU_SIZE);
120     }
121 
122     /**
123      * Set the desired LRU size.
124      *
125      * @param lruSize the LRU size
126      */
127     public LRUFilesCache(final int lruSize) {
128         this.lruSize = lruSize;
129     }
130 
131     @Override
132     public void putFile(final FileObject file) {
133         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
134 
135         writeLock.lock();
136         try {
137             files.put(file.getName(), file);
138         } finally {
139             writeLock.unlock();
140         }
141     }
142 
143     @Override
144     public boolean putFileIfAbsent(final FileObject file) {
145         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
146 
147         writeLock.lock();
148         try {
149             final FileName name = file.getName();
150 
151             if (files.containsKey(name)) {
152                 return false;
153             }
154 
155             files.put(name, file);
156             return true;
157         } finally {
158             writeLock.unlock();
159         }
160     }
161 
162     @Override
163     public FileObject getFile(final FileSystem filesystem, final FileName name) {
164         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(filesystem);
165 
166         readLock.lock();
167         try {
168             return files.get(name);
169         } finally {
170             readLock.unlock();
171         }
172     }
173 
174     @Override
175     public void clear(final FileSystem filesystem) {
176         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(filesystem);
177 
178         writeLock.lock();
179         try {
180             files.clear();
181 
182             filesystemCache.remove(filesystem);
183         } finally {
184             writeLock.unlock();
185         }
186     }
187 
188     protected Map<FileName, FileObject> getOrCreateFilesystemCache(final FileSystem filesystem) {
189         Map<FileName, FileObject> files = filesystemCache.get(filesystem);
190         if (files == null) {
191             files = new MyLRUMap(filesystem, lruSize);
192             filesystemCache.putIfAbsent(filesystem, files);
193         }
194         return files;
195     }
196 
197     @Override
198     public void close() {
199         super.close();
200         filesystemCache.clear();
201     }
202 
203     @Override
204     public void removeFile(final FileSystem filesystem, final FileName name) {
205         final Map<?, ?> files = getOrCreateFilesystemCache(filesystem);
206 
207         writeLock.lock();
208         try {
209             files.remove(name);
210 
211             if (files.size() < 1) {
212                 filesystemCache.remove(filesystem);
213             }
214         } finally {
215             writeLock.unlock();
216         }
217     }
218 
219     @Override
220     public void touchFile(final FileObject file) {
221         // this moves the file back on top
222         getFile(file.getFileSystem(), file.getName());
223     }
224 }