KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > opensymphony > oscache > plugins > diskpersistence > AbstractDiskPersistenceListener


1 /*
2  * Copyright (c) 2002-2003 by OpenSymphony
3  * All rights reserved.
4  */

5 package com.opensymphony.oscache.plugins.diskpersistence;
6
7 import com.opensymphony.oscache.base.Config;
8 import com.opensymphony.oscache.base.persistence.CachePersistenceException;
9 import com.opensymphony.oscache.base.persistence.PersistenceListener;
10 import com.opensymphony.oscache.web.ServletCacheAdministrator;
11
12 import org.apache.commons.logging.Log;
13 import org.apache.commons.logging.LogFactory;
14
15 import java.io.*;
16
17 import java.util.Set JavaDoc;
18
19 import javax.servlet.jsp.PageContext JavaDoc;
20
21 /**
22  * Persist the cache data to disk.
23  *
24  * The code in this class is totally not thread safe it is the resonsibility
25  * of the cache using this persistence listener to handle the concurrency.
26  *
27  * @author <a HREF="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
28  * @author <a HREF="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
29  * @author <a HREF="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
30  * @author <a HREF="mailto:amarch@soe.sony.com">Andres March</a>
31  */

32 public abstract class AbstractDiskPersistenceListener implements PersistenceListener, Serializable {
33     public final static String JavaDoc CACHE_PATH_KEY = "cache.path";
34
35     /**
36     * File extension for disk cache file
37     */

38     protected final static String JavaDoc CACHE_EXTENSION = "cache";
39
40     /**
41     * The directory that cache groups are stored under
42     */

43     protected final static String JavaDoc GROUP_DIRECTORY = "__groups__";
44
45     /**
46     * Sub path name for application cache
47     */

48     protected final static String JavaDoc APPLICATION_CACHE_SUBPATH = "application";
49
50     /**
51     * Sub path name for session cache
52     */

53     protected final static String JavaDoc SESSION_CACHE_SUBPATH = "session";
54
55     /**
56     * Property to get the temporary working directory of the servlet container.
57     */

58     protected static final String JavaDoc CONTEXT_TMPDIR = "javax.servlet.context.tempdir";
59     private static transient final Log log = LogFactory.getLog(AbstractDiskPersistenceListener.class);
60
61     /**
62     * Base path where the disk cache reside.
63     */

64     private File cachePath = null;
65     private File contextTmpDir;
66
67     /**
68     * Root path for disk cache
69     */

70     private String JavaDoc root = null;
71
72     /**
73     * Get the physical cache path on disk.
74     *
75     * @return A file representing the physical cache location.
76     */

77     public File getCachePath() {
78         return cachePath;
79     }
80
81     /**
82     * Get the root directory for persisting the cache on disk.
83     * This path includes scope and sessionId, if any.
84     *
85     * @return A String representing the root directory.
86     */

87     public String JavaDoc getRoot() {
88         return root;
89     }
90
91     /**
92     * Get the servlet context tmp directory.
93     *
94     * @return A file representing the servlet context tmp directory.
95     */

96     public File getContextTmpDir() {
97         return contextTmpDir;
98     }
99
100     /**
101     * Verify if a group exists in the cache
102     *
103     * @param group The group name to check
104     * @return True if it exists
105     * @throws CachePersistenceException
106     */

107     public boolean isGroupStored(String JavaDoc group) throws CachePersistenceException {
108         try {
109             File file = getCacheGroupFile(group);
110
111             return file.exists();
112         } catch (Exception JavaDoc e) {
113             throw new CachePersistenceException("Unable verify group '" + group + "' exists in the cache: " + e);
114         }
115     }
116
117     /**
118     * Verify if an object is currently stored in the cache
119     *
120     * @param key The object key
121     * @return True if it exists
122     * @throws CachePersistenceException
123     */

124     public boolean isStored(String JavaDoc key) throws CachePersistenceException {
125         try {
126             File file = getCacheFile(key);
127
128             return file.exists();
129         } catch (Exception JavaDoc e) {
130             throw new CachePersistenceException("Unable verify id '" + key + "' is stored in the cache: " + e);
131         }
132     }
133
134     /**
135     * Clears the whole cache directory, starting from the root
136     *
137     * @throws CachePersistenceException
138     */

139     public void clear() throws CachePersistenceException {
140         clear(root);
141     }
142
143     /**
144     * Initialises this <tt>DiskPersistenceListener</tt> using the supplied
145     * configuration.
146     *
147     * @param config The OSCache configuration
148     */

149     public PersistenceListener configure(Config config) {
150         String JavaDoc sessionId = null;
151         int scope = 0;
152         initFileCaching(config.getProperty(CACHE_PATH_KEY));
153
154         if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID) != null) {
155             sessionId = config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID);
156         }
157
158         if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE) != null) {
159             scope = Integer.parseInt(config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE));
160         }
161
162         StringBuffer JavaDoc root = new StringBuffer JavaDoc(getCachePath().getPath());
163         root.append("/");
164         root.append(getPathPart(scope));
165
166         if ((sessionId != null) && (sessionId.length() > 0)) {
167             root.append("/");
168             root.append(sessionId);
169         }
170
171         this.root = root.toString();
172         this.contextTmpDir = (File) config.get(ServletCacheAdministrator.HASH_KEY_CONTEXT_TMPDIR);
173
174         return this;
175     }
176
177     /**
178     * Delete a single cache entry.
179     *
180     * @param key The object key to delete
181     * @throws CachePersistenceException
182     */

183     public void remove(String JavaDoc key) throws CachePersistenceException {
184         File file = getCacheFile(key);
185         remove(file);
186     }
187
188     /**
189     * Deletes an entire group from the cache.
190     *
191     * @param groupName The name of the group to delete
192     * @throws CachePersistenceException
193     */

194     public void removeGroup(String JavaDoc groupName) throws CachePersistenceException {
195         File file = getCacheGroupFile(groupName);
196         remove(file);
197     }
198
199     /**
200     * Retrieve an object from the disk
201     *
202     * @param key The object key
203     * @return The retrieved object
204     * @throws CachePersistenceException
205     */

206     public Object JavaDoc retrieve(String JavaDoc key) throws CachePersistenceException {
207         return retrieve(getCacheFile(key));
208     }
209
210     /**
211     * Retrieves a group from the cache, or <code>null</code> if the group
212     * file could not be found.
213     *
214     * @param groupName The name of the group to retrieve.
215     * @return A <code>Set</code> containing keys of all of the cache
216     * entries that belong to this group.
217     * @throws CachePersistenceException
218     */

219     public Set JavaDoc retrieveGroup(String JavaDoc groupName) throws CachePersistenceException {
220         File groupFile = getCacheGroupFile(groupName);
221
222         try {
223             return (Set JavaDoc) retrieve(groupFile);
224         } catch (ClassCastException JavaDoc e) {
225             throw new CachePersistenceException("Group file " + groupFile + " was not persisted as a Set: " + e);
226         }
227     }
228
229     /**
230     * Stores an object in cache
231     *
232     * @param key The object's key
233     * @param obj The object to store
234     * @throws CachePersistenceException
235     */

236     public void store(String JavaDoc key, Object JavaDoc obj) throws CachePersistenceException {
237         File file = getCacheFile(key);
238         store(file, obj);
239     }
240
241     /**
242     * Stores a group in the persistent cache. This will overwrite any existing
243     * group with the same name
244     */

245     public void storeGroup(String JavaDoc groupName, Set JavaDoc group) throws CachePersistenceException {
246         File groupFile = getCacheGroupFile(groupName);
247         store(groupFile, group);
248     }
249
250     /**
251     * Allows to translate to the temp dir of the servlet container if cachePathStr
252     * is javax.servlet.context.tempdir.
253     *
254     * @param cachePathStr Cache path read from the properties file.
255     * @return Adjusted cache path
256     */

257     protected String JavaDoc adjustFileCachePath(String JavaDoc cachePathStr) {
258         if (cachePathStr.compareToIgnoreCase(CONTEXT_TMPDIR) == 0) {
259             cachePathStr = contextTmpDir.getAbsolutePath();
260         }
261
262         return cachePathStr;
263     }
264
265     /**
266     * Set caching to file on or off.
267     * If the <code>cache.path</code> property exists, we assume file caching is turned on.
268     * By the same token, to turn off file caching just remove this property.
269     */

270     protected void initFileCaching(String JavaDoc cachePathStr) {
271         if (cachePathStr != null) {
272             cachePath = new File(cachePathStr);
273
274             try {
275                 if (!cachePath.exists()) {
276                     if (log.isInfoEnabled()) {
277                         log.info("cache.path '" + cachePathStr + "' does not exist, creating");
278                     }
279
280                     cachePath.mkdirs();
281                 }
282
283                 if (!cachePath.isDirectory()) {
284                     log.error("cache.path '" + cachePathStr + "' is not a directory");
285                     cachePath = null;
286                 } else if (!cachePath.canWrite()) {
287                     log.error("cache.path '" + cachePathStr + "' is not a writable location");
288                     cachePath = null;
289                 }
290             } catch (Exception JavaDoc e) {
291                 log.error("cache.path '" + cachePathStr + "' could not be used", e);
292                 cachePath = null;
293             }
294         } else {
295             // Use default value
296
}
297     }
298     
299     // try 30s to delete the file
300
private static final long DELETE_THREAD_SLEEP = 500;
301     private static final int DELETE_COUNT = 60;
302
303     protected void remove(File file) throws CachePersistenceException {
304         int count = DELETE_COUNT;
305         try {
306             // Loop until we are able to delete (No current read).
307
// The cache must ensure that there are never two concurrent threads
308
// doing write (store and delete) operations on the same item.
309
// Delete only should be enough but file.exists prevents infinite loop
310
while (file.exists() && !file.delete() && count != 0) {
311                 count--;
312                 try {
313                     Thread.sleep(DELETE_THREAD_SLEEP);
314                 } catch (InterruptedException JavaDoc ignore) {
315                 }
316             }
317         } catch (Exception JavaDoc e) {
318             throw new CachePersistenceException("Unable to remove '" + file + "' from the cache: " + e);
319         }
320         if (file.exists() && count == 0) {
321             throw new CachePersistenceException("Unable to delete '" + file + "' from the cache. "+DELETE_COUNT+" attempts at "+DELETE_THREAD_SLEEP+" milliseconds intervals.");
322         }
323     }
324
325     /**
326     * Stores an object using the supplied file object
327     *
328     * @param file The file to use for storing the object
329     * @param obj the object to store
330     * @throws CachePersistenceException
331     */

332     protected void store(File file, Object JavaDoc obj) throws CachePersistenceException {
333         // check if file exists before testing if parent exists
334
if (!file.exists()) {
335             // check if the directory structure required exists and create it if it doesn't
336
File filepath = new File(file.getParent());
337
338             try {
339                 if (!filepath.exists()) {
340                     filepath.mkdirs();
341                 }
342             } catch (Exception JavaDoc e) {
343                 throw new CachePersistenceException("Unable to create the directory " + filepath);
344             }
345         }
346
347         // Write the object to disk
348
try {
349             FileOutputStream fout = new FileOutputStream(file);
350             try {
351                 ObjectOutputStream oout = new ObjectOutputStream(new BufferedOutputStream(fout));
352                 try {
353                     oout.writeObject(obj);
354                     oout.flush();
355                 } finally {
356                     try {
357                         oout.close();
358                     } catch (Exception JavaDoc e) {
359                     }
360                 }
361             } finally {
362                 try {
363                     fout.close();
364                 } catch (Exception JavaDoc e) {
365                 }
366             }
367         } catch (Exception JavaDoc e) {
368             int count = DELETE_COUNT;
369             while (file.exists() && !file.delete() && count != 0) {
370                 count--;
371                 try {
372                     Thread.sleep(DELETE_THREAD_SLEEP);
373                 } catch (InterruptedException JavaDoc ignore) {
374                 }
375             }
376             throw new CachePersistenceException("Unable to write '" + file + "' in the cache. Exception: " + e.getClass().getName() + ", Message: " + e.getMessage());
377         }
378     }
379
380     /**
381     * Build fully qualified cache file for the specified cache entry key.
382     *
383     * @param key Cache Entry Key.
384     * @return File reference.
385     */

386     protected File getCacheFile(String JavaDoc key) {
387         char[] fileChars = getCacheFileName(key);
388
389         File file = new File(root, new String JavaDoc(fileChars) + "." + CACHE_EXTENSION);
390
391         return file;
392     }
393
394     /**
395     * Build cache file name for the specified cache entry key.
396     *
397     * @param key Cache Entry Key.
398     * @return char[] file name.
399     */

400     protected abstract char[] getCacheFileName(String JavaDoc key);
401
402     /**
403     * Builds a fully qualified file name that specifies a cache group entry.
404     *
405     * @param group The name of the group
406     * @return A File reference
407     */

408     private File getCacheGroupFile(String JavaDoc group) {
409         int AVERAGE_PATH_LENGTH = 30;
410
411         if ((group == null) || (group.length() == 0)) {
412             throw new IllegalArgumentException JavaDoc("Invalid group '" + group + "' specified to getCacheGroupFile.");
413         }
414
415         StringBuffer JavaDoc path = new StringBuffer JavaDoc(AVERAGE_PATH_LENGTH);
416
417         // Build a fully qualified file name for this group
418
path.append(GROUP_DIRECTORY).append('/');
419         path.append(group).append('.').append(CACHE_EXTENSION);
420
421         return new File(root, path.toString());
422     }
423
424     /**
425     * This allows to persist different scopes in different path in the case of
426     * file caching.
427     *
428     * @param scope Cache scope.
429     * @return The scope subpath
430     */

431     private String JavaDoc getPathPart(int scope) {
432         if (scope == PageContext.SESSION_SCOPE) {
433             return SESSION_CACHE_SUBPATH;
434         } else {
435             return APPLICATION_CACHE_SUBPATH;
436         }
437     }
438
439     /**
440     * Clears a whole directory, starting from the specified
441     * directory
442     *
443     * @param baseDirName The root directory to delete
444     * @throws CachePersistenceException
445     */

446     private void clear(String JavaDoc baseDirName) throws CachePersistenceException {
447         File baseDir = new File(baseDirName);
448         File[] fileList = baseDir.listFiles();
449
450         try {
451             if (fileList != null) {
452                 // Loop through all the files and directory to delete them
453
for (int count = 0; count < fileList.length; count++) {
454                     if (fileList[count].isFile()) {
455                         fileList[count].delete();
456                     } else {
457                         // Make a recursive call to delete the directory
458
clear(fileList[count].toString());
459                         fileList[count].delete();
460                     }
461                 }
462             }
463
464             // Delete the root directory
465
baseDir.delete();
466         } catch (Exception JavaDoc e) {
467             throw new CachePersistenceException("Unable to clear the cache directory");
468         }
469     }
470
471     /**
472     * Retrives a serialized object from the supplied file, or returns
473     * <code>null</code> if the file does not exist.
474     *
475     * @param file The file to deserialize
476     * @return The deserialized object
477     * @throws CachePersistenceException
478     */

479     private Object JavaDoc retrieve(File file) throws CachePersistenceException {
480         Object JavaDoc readContent = null;
481         boolean fileExist;
482
483         try {
484             fileExist = file.exists();
485         } catch (Exception JavaDoc e) {
486             throw new CachePersistenceException("Unable to verify if " + file + " exists: " + e);
487         }
488
489         // Read the file if it exists
490
if (fileExist) {
491             ObjectInputStream oin = null;
492
493             try {
494                 BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
495                 oin = new ObjectInputStream(in);
496                 readContent = oin.readObject();
497             } catch (Exception JavaDoc e) {
498                 // We expect this exception to occur.
499
// This is when the item will be invalidated (written or deleted)
500
// during read.
501
// The cache has the logic to retry reading.
502
throw new CachePersistenceException("Unable to read '" + file.getAbsolutePath() + "' from the cache: " + e);
503             } finally {
504               // HHDE: no need to close in. Will be closed by oin
505
try {
506                     oin.close();
507                 } catch (Exception JavaDoc ex) {
508                 }
509                 }
510             }
511
512         return readContent;
513     }
514 }
515
Popular Tags