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.io.IOUtils;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.apache.commons.vfs2.FileName;
032import org.apache.commons.vfs2.FileObject;
033import org.apache.commons.vfs2.FileSystem;
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                    // force detach
081                    IOUtils.closeQuietly(fileObject, e -> VfsLog.warn(getLogger(), log, Messages.getString("vfs.impl/LRUFilesCache-remove-ex.warn"), e));
082                    final Map<?, ?> files = fileSystemCache.get(filesystem);
083                    if (files.isEmpty()) {
084                        fileSystemCache.remove(filesystem);
085                    }
086                    return true;
087                }
088
089                return false;
090            }
091        }
092    }
093
094    /** The default LRU size */
095    private static final int DEFAULT_LRU_SIZE = 100;
096
097    /** The logger to use. */
098    private static final Log log = LogFactory.getLog(LRUFilesCache.class);
099
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}