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