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.util.Map;
020import java.util.concurrent.ConcurrentHashMap;
021import java.util.concurrent.ConcurrentMap;
022import java.util.concurrent.locks.Lock;
023import java.util.concurrent.locks.ReadWriteLock;
024import java.util.concurrent.locks.ReentrantReadWriteLock;
025
026import org.apache.commons.collections4.map.AbstractLinkedMap;
027import org.apache.commons.collections4.map.LRUMap;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.apache.commons.vfs2.FileName;
031import org.apache.commons.vfs2.FileObject;
032import org.apache.commons.vfs2.FileSystem;
033import org.apache.commons.vfs2.FileSystemException;
034import org.apache.commons.vfs2.VfsLog;
035import org.apache.commons.vfs2.util.Messages;
036
037/**
038 * This implementation caches every file using {@link LRUMap}.
039 * <p>
040 * The default constructor uses a LRU size of 100 per file system.
041 * </p>
042 */
043public class LRUFilesCache extends AbstractFilesCache {
044
045    /**
046     * The file cache
047     */
048    private class MyLRUMap extends LRUMap<FileName, FileObject> {
049        /**
050         * serialVersionUID format is YYYYMMDD for the date of the last binary change.
051         */
052        private static final long serialVersionUID = 20101208L;
053
054        /** The FileSystem */
055        private final FileSystem filesystem;
056
057        MyLRUMap(final FileSystem filesystem, final int size) {
058            super(size, true);
059            this.filesystem = filesystem;
060        }
061
062        @Override
063        protected boolean removeLRU(final AbstractLinkedMap.LinkEntry<FileName, FileObject> linkEntry) {
064            synchronized (LRUFilesCache.this) {
065                @SuppressWarnings("resource") // FileObject allocated elsewhere.
066                final FileObject fileObject = linkEntry.getValue();
067
068                // System.err.println(">>> " + size() + " check removeLRU:" + linkEntry.getKey().toString());
069
070                if (fileObject.isAttached() || fileObject.isContentOpen()) {
071                    // do not allow open or attached files to be removed
072                    // System.err.println(">>> " + size() + " VETO removeLRU:" +
073                    // linkEntry.getKey().toString() + " (" + file.isAttached() + "/" +
074                    // file.isContentOpen() + ")");
075                    return false;
076                }
077
078                // System.err.println(">>> " + size() + " removeLRU:" + linkEntry.getKey().toString());
079                if (super.removeLRU(linkEntry)) {
080                    try {
081                        // force detach
082                        fileObject.close();
083                    } catch (final FileSystemException e) {
084                        VfsLog.warn(getLogger(), log, Messages.getString("vfs.impl/LRUFilesCache-remove-ex.warn"), e);
085                    }
086
087                    final Map<?, ?> files = fileSystemCache.get(filesystem);
088                    if (files.isEmpty()) {
089                        fileSystemCache.remove(filesystem);
090                    }
091
092                    return true;
093                }
094
095                return false;
096            }
097        }
098    }
099
100    /** The default LRU size */
101    private static final int DEFAULT_LRU_SIZE = 100;
102
103    /** The logger to use. */
104    private static final Log log = LogFactory.getLog(LRUFilesCache.class);
105
106    /** The FileSystem cache */
107    private final ConcurrentMap<FileSystem, Map<FileName, FileObject>> fileSystemCache = new ConcurrentHashMap<>();
108
109    /** The size of the cache */
110    private final int lruSize;
111    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
112
113    /**
114     * Constructs a new instance. Uses an LRU size of 100 per file system.
115     */
116    public LRUFilesCache() {
117        this(DEFAULT_LRU_SIZE);
118    }
119
120    /**
121     * Constructs a new instance with the desired LRU size.
122     *
123     * @param lruSize the LRU size
124     */
125    public LRUFilesCache(final int lruSize) {
126        this.lruSize = lruSize;
127    }
128
129    @Override
130    public void clear(final FileSystem filesystem) {
131        final Map<FileName, FileObject> files = getOrCreateFilesystemCache(filesystem);
132
133        writeLock().lock();
134        try {
135            files.clear();
136
137            fileSystemCache.remove(filesystem);
138        } finally {
139            writeLock().unlock();
140        }
141    }
142
143    @Override
144    public void close() {
145        super.close();
146        fileSystemCache.clear();
147    }
148
149    @Override
150    public FileObject getFile(final FileSystem filesystem, final FileName name) {
151        final Map<FileName, FileObject> files = getOrCreateFilesystemCache(filesystem);
152        readLock().lock();
153        try {
154            return files.get(name);
155        } finally {
156            readLock().unlock();
157        }
158    }
159
160    /**
161     * Gets or creates a new Map.
162     *
163     * @param fileSystem the key
164     * @return an existing or new Map.
165     */
166    protected Map<FileName, FileObject> getOrCreateFilesystemCache(final FileSystem fileSystem) {
167        return fileSystemCache.computeIfAbsent(fileSystem, k -> new MyLRUMap(k, lruSize));
168    }
169
170    @Override
171    public void putFile(final FileObject file) {
172        final Map<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
173
174        writeLock().lock();
175        try {
176            files.put(file.getName(), file);
177        } finally {
178            writeLock().unlock();
179        }
180    }
181
182    @Override
183    public boolean putFileIfAbsent(final FileObject file) {
184        final Map<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
185
186        writeLock().lock();
187        try {
188            return files.putIfAbsent(file.getName(), file) == null;
189        } finally {
190            writeLock().unlock();
191        }
192    }
193
194    private Lock readLock() {
195        return rwLock.readLock();
196    }
197
198    @Override
199    public void removeFile(final FileSystem filesystem, final FileName name) {
200        final Map<?, ?> files = getOrCreateFilesystemCache(filesystem);
201
202        writeLock().lock();
203        try {
204            files.remove(name);
205
206            if (files.isEmpty()) {
207                fileSystemCache.remove(filesystem);
208            }
209        } finally {
210            writeLock().unlock();
211        }
212    }
213
214    @Override
215    public void touchFile(final FileObject file) {
216        // this moves the file back on top
217        getFile(file.getFileSystem(), file.getName());
218    }
219
220    private Lock writeLock() {
221        return rwLock.writeLock();
222    }
223}