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}