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                  @SuppressWarnings("resource") // FileObject allocated elsewhere.
82                  final FileObject fileObject = linkEntry.getValue();
83  
84                  // System.err.println(">>> " + size() + " check removeLRU:" + linkEntry.getKey().toString());
85  
86                  if (fileObject.isAttached() || fileObject.isContentOpen()) {
87                      // do not allow open or attached files to be removed
88                      // System.err.println(">>> " + size() + " VETO removeLRU:" +
89                      // linkEntry.getKey().toString() + " (" + file.isAttached() + "/" +
90                      // file.isContentOpen() + ")");
91                      return false;
92                  }
93  
94                  // System.err.println(">>> " + size() + " removeLRU:" + linkEntry.getKey().toString());
95                  if (super.removeLRU(linkEntry)) {
96                      try {
97                          // force detach
98                          fileObject.close();
99                      } catch (final FileSystemException e) {
100                         VfsLog.warn(getLogger(), log, Messages.getString("vfs.impl/LRUFilesCache-remove-ex.warn"), e);
101                     }
102 
103                     final Map<?, ?> files = filesystemCache.get(filesystem);
104                     if (files.isEmpty()) {
105                         filesystemCache.remove(filesystem);
106                     }
107 
108                     return true;
109                 }
110 
111                 return false;
112             }
113         }
114     }
115 
116     /**
117      * Default constructor. Uses a LRU size of 100 per file system.
118      */
119     public LRUFilesCache() {
120         this(DEFAULT_LRU_SIZE);
121     }
122 
123     /**
124      * Set the desired LRU size.
125      *
126      * @param lruSize the LRU size
127      */
128     public LRUFilesCache(final int lruSize) {
129         this.lruSize = lruSize;
130     }
131 
132     @Override
133     public void putFile(final FileObject file) {
134         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
135 
136         writeLock.lock();
137         try {
138             files.put(file.getName(), file);
139         } finally {
140             writeLock.unlock();
141         }
142     }
143 
144     @Override
145     public boolean putFileIfAbsent(final FileObject file) {
146         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
147 
148         writeLock.lock();
149         try {
150             final FileName name = file.getName();
151 
152             if (files.containsKey(name)) {
153                 return false;
154             }
155 
156             files.put(name, file);
157             return true;
158         } finally {
159             writeLock.unlock();
160         }
161     }
162 
163     @Override
164     public FileObject getFile(final FileSystem filesystem, final FileName name) {
165         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(filesystem);
166 
167         readLock.lock();
168         try {
169             return files.get(name);
170         } finally {
171             readLock.unlock();
172         }
173     }
174 
175     @Override
176     public void clear(final FileSystem filesystem) {
177         final Map<FileName, FileObject> files = getOrCreateFilesystemCache(filesystem);
178 
179         writeLock.lock();
180         try {
181             files.clear();
182 
183             filesystemCache.remove(filesystem);
184         } finally {
185             writeLock.unlock();
186         }
187     }
188 
189     protected Map<FileName, FileObject> getOrCreateFilesystemCache(final FileSystem filesystem) {
190         Map<FileName, FileObject> files = filesystemCache.get(filesystem);
191         if (files == null) {
192             files = new MyLRUMap(filesystem, lruSize);
193             filesystemCache.putIfAbsent(filesystem, files);
194         }
195         return files;
196     }
197 
198     @Override
199     public void close() {
200         super.close();
201         filesystemCache.clear();
202     }
203 
204     @Override
205     public void removeFile(final FileSystem filesystem, final FileName name) {
206         final Map<?, ?> files = getOrCreateFilesystemCache(filesystem);
207 
208         writeLock.lock();
209         try {
210             files.remove(name);
211 
212             if (files.isEmpty()) {
213                 filesystemCache.remove(filesystem);
214             }
215         } finally {
216             writeLock.unlock();
217         }
218     }
219 
220     @Override
221     public void touchFile(final FileObject file) {
222         // this moves the file back on top
223         getFile(file.getFileSystem(), file.getName());
224     }
225 }