1 /* CacheLRUWrapper -- Handle LRU for cache files. 2 Copyright (C) 2011 Red Hat, Inc. 3 4 This file is part of IcedTea. 5 6 IcedTea is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, version 2. 9 10 IcedTea is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with IcedTea; see the file COPYING. If not, write to 17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 02110-1301 USA. 19 20 Linking this library statically or dynamically with other modules is 21 making a combined work based on this library. Thus, the terms and 22 conditions of the GNU General Public License cover the whole 23 combination. 24 25 As a special exception, the copyright holders of this library give you 26 permission to link this library with independent modules to produce an 27 executable, regardless of the license terms of these independent 28 modules, and to copy and distribute the resulting executable under 29 terms of your choice, provided that you also meet, for each linked 30 independent module, the terms and conditions of the license of that 31 module. An independent module is a module which is not derived from 32 or based on this library. If you modify this library, you may extend 33 this exception to your version of the library, but you are not 34 obligated to do so. If you do not wish to do so, delete this 35 exception statement from your version. 36 */ 37 package net.sourceforge.jnlp.cache; 38 39 import static net.sourceforge.jnlp.runtime.Translator.R; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.util.AbstractMap; 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.Comparator; 47 import java.util.Iterator; 48 import java.util.List; 49 import java.util.Map.Entry; 50 import java.util.Set; 51 import net.sourceforge.jnlp.config.InfrastructureFileDescriptor; 52 53 import net.sourceforge.jnlp.config.PathsAndFiles; 54 import net.sourceforge.jnlp.util.FileUtils; 55 import net.sourceforge.jnlp.util.PropertiesFile; 56 import net.sourceforge.jnlp.util.logging.OutputController; 57 58 /** 59 * This class helps maintain the ordering of most recently use items across 60 * multiple jvm instances. 61 * 62 */ 63 public class CacheLRUWrapper { 64 65 /* 66 * back-end of how LRU is implemented This file is to keep track of the most 67 * recently used items. The items are to be kept with key = (current time 68 * accessed) followed by folder of item. value = path to file. 69 */ 70 71 private final InfrastructureFileDescriptor recentlyUsedPropertiesFile; 72 private final InfrastructureFileDescriptor cacheDir; 73 CacheLRUWrapper()74 public CacheLRUWrapper() { 75 this(PathsAndFiles.getRecentlyUsedFile(), PathsAndFiles.CACHE_DIR); 76 } 77 78 79 /** 80 * testing constructor 81 * @param recentlyUsed file to be used as recently_used file 82 * @param cacheDir dir with cache 83 */ CacheLRUWrapper(final InfrastructureFileDescriptor recentlyUsed, final InfrastructureFileDescriptor cacheDir)84 public CacheLRUWrapper(final InfrastructureFileDescriptor recentlyUsed, final InfrastructureFileDescriptor cacheDir) { 85 recentlyUsedPropertiesFile = recentlyUsed; 86 this.cacheDir = cacheDir; 87 if (!recentlyUsed.getFile().exists()) { 88 try { 89 FileUtils.createParentDir(recentlyUsed.getFile()); 90 FileUtils.createRestrictedFile(recentlyUsed.getFile(), true); 91 } catch (IOException e) { 92 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); 93 } 94 } 95 } 96 97 /** 98 * Returns an instance of the policy. 99 * 100 * @return an instance of the policy 101 */ getInstance()102 public static CacheLRUWrapper getInstance() { 103 return CacheLRUWrapperHolder.INSTANCE; 104 } 105 106 107 private PropertiesFile cachedRecentlyUsedPropertiesFile = null ; 108 /** 109 * @return the recentlyUsedPropertiesFile 110 */ getRecentlyUsedPropertiesFile()111 synchronized PropertiesFile getRecentlyUsedPropertiesFile() { 112 if (cachedRecentlyUsedPropertiesFile == null) { 113 //no properties file yet, create it 114 cachedRecentlyUsedPropertiesFile = new PropertiesFile(recentlyUsedPropertiesFile.getFile()); 115 return cachedRecentlyUsedPropertiesFile; 116 } 117 if (recentlyUsedPropertiesFile.getFile().equals(cachedRecentlyUsedPropertiesFile.getStoreFile())){ 118 //The underlying InfrastructureFileDescriptor is still pointing to the same file, use current properties file 119 return cachedRecentlyUsedPropertiesFile; 120 } else { 121 //the InfrastructureFileDescriptor was set to different location, move to it 122 if (cachedRecentlyUsedPropertiesFile.tryLock()) { 123 cachedRecentlyUsedPropertiesFile.store(); 124 cachedRecentlyUsedPropertiesFile.unlock(); 125 } 126 cachedRecentlyUsedPropertiesFile = new PropertiesFile(recentlyUsedPropertiesFile.getFile()); 127 return cachedRecentlyUsedPropertiesFile; 128 } 129 130 } 131 132 /** 133 * @return the cacheDir 134 */ getCacheDir()135 public InfrastructureFileDescriptor getCacheDir() { 136 return cacheDir; 137 } 138 139 /** 140 * @return the recentlyUsedFile 141 */ getRecentlyUsedFile()142 public InfrastructureFileDescriptor getRecentlyUsedFile() { 143 return recentlyUsedPropertiesFile; 144 } 145 146 private static class CacheLRUWrapperHolder{ 147 private static final CacheLRUWrapper INSTANCE = new CacheLRUWrapper(); 148 } 149 150 /** 151 * Update map for keeping track of recently used items. 152 */ load()153 public synchronized void load() { 154 boolean loaded = getRecentlyUsedPropertiesFile().load(); 155 /* 156 * clean up possibly corrupted entries 157 */ 158 if (loaded && checkData()) { 159 OutputController.getLogger().log(new LruCacheException()); 160 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("CFakeCache")); 161 store(); 162 OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, R("CFakedCache")); 163 } 164 } 165 166 /** 167 * check content of recentlyUsedPropertiesFile and remove invalid/corrupt entries 168 * 169 * @return true, if cache was corrupted and affected entry removed 170 */ checkData()171 private boolean checkData () { 172 boolean modified = false; 173 Set<Entry<Object, Object>> q = getRecentlyUsedPropertiesFile().entrySet(); 174 for (Iterator<Entry<Object, Object>> it = q.iterator(); it.hasNext();) { 175 Entry<Object, Object> currentEntry = it.next(); 176 177 final String key = (String) currentEntry.getKey(); 178 final String path = (String) currentEntry.getValue(); 179 180 // 1. check key format: "milliseconds,number" 181 try { 182 String sa[] = key.split(","); 183 Long l1 = Long.parseLong(sa[0]); 184 Long l2 = Long.parseLong(sa[1]); 185 } catch (Exception ex) { 186 it.remove(); 187 modified = true; 188 continue; 189 } 190 191 // 2. check path format - does the path look correct? 192 if (path != null) { 193 if (!path.contains(getCacheDir().getFullPath())) { 194 it.remove(); 195 modified = true; 196 } 197 } else { 198 it.remove(); 199 modified = true; 200 } 201 } 202 203 return modified; 204 } 205 206 /** 207 * Write file to disk. 208 * @return true if properties were successfully stored, false otherwise 209 */ store()210 public synchronized boolean store() { 211 if (getRecentlyUsedPropertiesFile().isHeldByCurrentThread()) { 212 getRecentlyUsedPropertiesFile().store(); 213 return true; 214 } 215 return false; 216 } 217 218 /** 219 * This adds a new entry to file. 220 * 221 * @param key key we want path to be associated with. 222 * @param path path to cache item. 223 * @return true if we successfully added to map, false otherwise. 224 */ addEntry(String key, String path)225 public synchronized boolean addEntry(String key, String path) { 226 PropertiesFile props = getRecentlyUsedPropertiesFile(); 227 if (props.containsKey(key)) { 228 return false; 229 } 230 props.setProperty(key, path); 231 return true; 232 } 233 234 /** 235 * This removed an entry from our map. 236 * 237 * @param key key we want to remove. 238 * @return true if we successfully removed key from map, false otherwise. 239 */ removeEntry(String key)240 public synchronized boolean removeEntry(String key) { 241 PropertiesFile props = getRecentlyUsedPropertiesFile(); 242 if (!props.containsKey(key)) { 243 return false; 244 } 245 props.remove(key); 246 return true; 247 } 248 getIdForCacheFolder(String folder)249 private String getIdForCacheFolder(String folder) { 250 int len = getCacheDir().getFullPath().length(); 251 int index = folder.indexOf(File.separatorChar, len + 1); 252 return folder.substring(len + 1, index); 253 } 254 255 /** 256 * This updates the given key to reflect it was recently accessed. 257 * 258 * @param oldKey Key we wish to update. 259 * @return true if we successfully updated value, false otherwise. 260 */ updateEntry(String oldKey)261 public synchronized boolean updateEntry(String oldKey) { 262 PropertiesFile props = getRecentlyUsedPropertiesFile(); 263 if (!props.containsKey(oldKey)) { 264 return false; 265 } 266 String value = props.getProperty(oldKey); 267 String folder = getIdForCacheFolder(value); 268 269 props.remove(oldKey); 270 props.setProperty(Long.toString(System.currentTimeMillis()) + "," + folder, value); 271 return true; 272 } 273 274 /** 275 * Return a copy of the keys available. 276 * 277 * @return List of Strings sorted by ascending order. 278 */ 279 @SuppressWarnings({"unchecked", "rawtypes"}) 280 //although Properties are pretending to be <object,Object> they are always <String,String> 281 //bug in jdk? getLRUSortedEntries()282 public synchronized List<Entry<String, String>> getLRUSortedEntries() { 283 List<Entry<String, String>> entries = new ArrayList<>(); 284 285 for (Entry e : getRecentlyUsedPropertiesFile().entrySet()) { 286 entries.add(new AbstractMap.SimpleImmutableEntry(e)); 287 } 288 289 // sort by keys in descending order. 290 Collections.sort(entries, new Comparator<Entry<String, String>>() { 291 @Override 292 public int compare(Entry<String, String> e1, Entry<String, String> e2) { 293 Long t1 = Long.parseLong(e1.getKey().split(",")[0]); 294 Long t2 = Long.parseLong(e2.getKey().split(",")[0]); 295 296 int c = t1.compareTo(t2); 297 return c < 0 ? 1 : (c > 0 ? -1 : 0); 298 } 299 }); 300 return entries; 301 } 302 303 /** 304 * Lock the file to have exclusive access. 305 */ lock()306 public synchronized void lock() { 307 getRecentlyUsedPropertiesFile().lock(); 308 } 309 310 /** 311 * Unlock the file. 312 */ unlock()313 public synchronized void unlock() { 314 getRecentlyUsedPropertiesFile().unlock(); 315 } 316 317 /** 318 * Return the value of given key. 319 * 320 * @param key key of property 321 * @return value of given key, null otherwise. 322 */ getValue(String key)323 public synchronized String getValue(String key) { 324 return getRecentlyUsedPropertiesFile().getProperty(key); 325 } 326 containsKey(String key)327 public synchronized boolean containsKey(String key) { 328 return getRecentlyUsedPropertiesFile().containsKey(key); 329 } 330 containsValue(String value)331 public synchronized boolean containsValue(String value) { 332 return getRecentlyUsedPropertiesFile().containsValue(value); 333 } 334 335 /** 336 * Generate a key given the path to file. May or may not generate the same 337 * key given same path. 338 * 339 * @param path Path to generate a key with. 340 * @return String representing the a key. 341 */ generateKey(String path)342 public String generateKey(String path) { 343 return System.currentTimeMillis() + "," + getIdForCacheFolder(path); 344 } 345 clearLRUSortedEntries()346 void clearLRUSortedEntries() { 347 getRecentlyUsedPropertiesFile().clear(); 348 } 349 } 350