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}