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