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