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}