|
|||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
AbstractDiskPersistenceListener.java | 57.9% | 72.4% | 77.3% | 69.9% |
|
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;
|
|
18 |
|
|
19 |
import javax.servlet.jsp.PageContext;
|
|
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="mailto:chris@swebtec.com">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 CACHE_PATH_KEY = "cache.path"; |
|
34 |
|
|
35 |
/**
|
|
36 |
* File extension for disk cache file
|
|
37 |
*/
|
|
38 |
protected final static String CACHE_EXTENSION = "cache"; |
|
39 |
|
|
40 |
/**
|
|
41 |
* The directory that cache groups are stored under
|
|
42 |
*/
|
|
43 |
protected final static String GROUP_DIRECTORY = "__groups__"; |
|
44 |
|
|
45 |
/**
|
|
46 |
* Sub path name for application cache
|
|
47 |
*/
|
|
48 |
protected final static String APPLICATION_CACHE_SUBPATH = "application"; |
|
49 |
|
|
50 |
/**
|
|
51 |
* Sub path name for session cache
|
|
52 |
*/
|
|
53 |
protected final static String SESSION_CACHE_SUBPATH = "session"; |
|
54 |
|
|
55 |
/**
|
|
56 |
* Property to get the temporary working directory of the servlet container.
|
|
57 |
*/
|
|
58 |
protected static final String 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 root = null; |
|
71 |
|
|
72 |
/**
|
|
73 |
* Get the physical cache path on disk.
|
|
74 |
*
|
|
75 |
* @return A file representing the physical cache location.
|
|
76 |
*/
|
|
77 | 78 |
public File getCachePath() {
|
78 | 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 | 0 |
public String getRoot() {
|
88 | 0 |
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 | 0 |
public File getContextTmpDir() {
|
97 | 0 |
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 | 0 |
public boolean isGroupStored(String group) throws CachePersistenceException { |
108 | 0 |
try {
|
109 | 0 |
File file = getCacheGroupFile(group); |
110 |
|
|
111 | 0 |
return file.exists();
|
112 |
} catch (Exception e) {
|
|
113 | 0 |
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 | 64 |
public boolean isStored(String key) throws CachePersistenceException { |
125 | 64 |
try {
|
126 | 64 |
File file = getCacheFile(key); |
127 |
|
|
128 | 64 |
return file.exists();
|
129 |
} catch (Exception e) {
|
|
130 | 0 |
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 | 73 |
public void clear() throws CachePersistenceException { |
140 | 73 |
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 | 78 |
public PersistenceListener configure(Config config) {
|
150 | 78 |
String sessionId = null;
|
151 | 78 |
int scope = 0;
|
152 | 78 |
initFileCaching(config.getProperty(CACHE_PATH_KEY)); |
153 |
|
|
154 | 78 |
if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID) != null) { |
155 | 0 |
sessionId = config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID); |
156 |
} |
|
157 |
|
|
158 | 78 |
if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE) != null) { |
159 | 0 |
scope = Integer.parseInt(config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE)); |
160 |
} |
|
161 |
|
|
162 | 78 |
StringBuffer root = new StringBuffer(getCachePath().getPath());
|
163 | 78 |
root.append("/");
|
164 | 78 |
root.append(getPathPart(scope)); |
165 |
|
|
166 | 78 |
if ((sessionId != null) && (sessionId.length() > 0)) { |
167 | 0 |
root.append("/");
|
168 | 0 |
root.append(sessionId); |
169 |
} |
|
170 |
|
|
171 | 78 |
this.root = root.toString();
|
172 | 78 |
this.contextTmpDir = (File) config.get(ServletCacheAdministrator.HASH_KEY_CONTEXT_TMPDIR);
|
173 |
|
|
174 | 78 |
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 | 22 |
public void remove(String key) throws CachePersistenceException { |
184 | 22 |
File file = getCacheFile(key); |
185 | 22 |
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 | 0 |
public void removeGroup(String groupName) throws CachePersistenceException { |
195 | 0 |
File file = getCacheGroupFile(groupName); |
196 | 0 |
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 | 383 |
public Object retrieve(String key) throws CachePersistenceException { |
207 | 383 |
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 | 109 |
public Set retrieveGroup(String groupName) throws CachePersistenceException { |
220 | 109 |
File groupFile = getCacheGroupFile(groupName); |
221 |
|
|
222 | 109 |
try {
|
223 | 109 |
return (Set) retrieve(groupFile);
|
224 |
} catch (ClassCastException e) {
|
|
225 | 0 |
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 | 182 |
public void store(String key, Object obj) throws CachePersistenceException { |
237 | 182 |
File file = getCacheFile(key); |
238 | 182 |
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 | 98 |
public void storeGroup(String groupName, Set group) throws CachePersistenceException { |
246 | 98 |
File groupFile = getCacheGroupFile(groupName); |
247 | 98 |
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 | 0 |
protected String adjustFileCachePath(String cachePathStr) {
|
258 | 0 |
if (cachePathStr.compareToIgnoreCase(CONTEXT_TMPDIR) == 0) {
|
259 | 0 |
cachePathStr = contextTmpDir.getAbsolutePath(); |
260 |
} |
|
261 |
|
|
262 | 0 |
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 | 78 |
protected void initFileCaching(String cachePathStr) { |
271 | 78 |
if (cachePathStr != null) { |
272 | 78 |
cachePath = new File(cachePathStr);
|
273 |
|
|
274 | 78 |
try {
|
275 | 78 |
if (!cachePath.exists()) {
|
276 | 4 |
if (log.isInfoEnabled()) {
|
277 | 4 |
log.info("cache.path '" + cachePathStr + "' does not exist, creating"); |
278 |
} |
|
279 |
|
|
280 | 4 |
cachePath.mkdirs(); |
281 |
} |
|
282 |
|
|
283 | 78 |
if (!cachePath.isDirectory()) {
|
284 | 0 |
log.error("cache.path '" + cachePathStr + "' is not a directory"); |
285 | 0 |
cachePath = null;
|
286 | 78 |
} else if (!cachePath.canWrite()) { |
287 | 0 |
log.error("cache.path '" + cachePathStr + "' is not a writable location"); |
288 | 0 |
cachePath = null;
|
289 |
} |
|
290 |
} catch (Exception e) {
|
|
291 | 0 |
log.error("cache.path '" + cachePathStr + "' could not be used", e); |
292 | 0 |
cachePath = null;
|
293 |
} |
|
294 |
} else {
|
|
295 |
// Use default value
|
|
296 |
} |
|
297 |
} |
|
298 |
|
|
299 | 22 |
protected void remove(File file) throws CachePersistenceException { |
300 | 22 |
try {
|
301 |
// Loop until we are able to delete (No current read).
|
|
302 |
// The cache must ensure that there are never two concurrent threads
|
|
303 |
// doing write (store and delete) operations on the same item.
|
|
304 |
// Delete only should be enough but file.exists prevents infinite loop
|
|
305 | 22 |
while (!file.delete() && file.exists()) {
|
306 |
; |
|
307 |
} |
|
308 |
} catch (Exception e) {
|
|
309 | 0 |
throw new CachePersistenceException("Unable to remove '" + file + "' from the cache: " + e); |
310 |
} |
|
311 |
} |
|
312 |
|
|
313 |
/**
|
|
314 |
* Stores an object using the supplied file object
|
|
315 |
*
|
|
316 |
* @param file The file to use for storing the object
|
|
317 |
* @param obj the object to store
|
|
318 |
* @throws CachePersistenceException
|
|
319 |
*/
|
|
320 | 280 |
protected void store(File file, Object obj) throws CachePersistenceException { |
321 |
// check if the directory structure required exists and create it if it doesn't
|
|
322 | 280 |
File filepath = new File(file.getParent());
|
323 |
|
|
324 | 280 |
try {
|
325 | 280 |
if (!filepath.exists()) {
|
326 | 27 |
filepath.mkdirs(); |
327 |
} |
|
328 |
} catch (Exception e) {
|
|
329 | 0 |
throw new CachePersistenceException("Unable to create the directory " + filepath); |
330 |
} |
|
331 |
|
|
332 |
// Loop until we are able to delete (No current read).
|
|
333 |
// The cache must ensure that there are never two concurrent threads
|
|
334 |
// doing write (store and delete) operations on the same item.
|
|
335 |
// Delete only should be enough but file.exists prevents infinite loop
|
|
336 | 280 |
while (file.exists() && !file.delete()) {
|
337 |
; |
|
338 |
} |
|
339 |
|
|
340 |
// Write the object to disk
|
|
341 | 280 |
FileOutputStream fout = null;
|
342 | 280 |
ObjectOutputStream oout = null;
|
343 |
|
|
344 | 280 |
try {
|
345 | 280 |
fout = new FileOutputStream(file);
|
346 | 280 |
oout = new ObjectOutputStream(fout);
|
347 | 280 |
oout.writeObject(obj); |
348 | 280 |
oout.flush(); |
349 |
} catch (Exception e) {
|
|
350 | 0 |
while (file.exists() && !file.delete()) {
|
351 |
; |
|
352 |
} |
|
353 |
|
|
354 | 0 |
throw new CachePersistenceException("Unable to write '" + file + "' in the cache. Exception: " + e.getClass().getName() + ", Message: " + e.getMessage()); |
355 |
} finally {
|
|
356 | 280 |
try {
|
357 | 280 |
fout.close(); |
358 |
} catch (Exception e) {
|
|
359 |
} |
|
360 |
|
|
361 | 280 |
try {
|
362 | 280 |
oout.close(); |
363 |
} catch (Exception e) {
|
|
364 |
} |
|
365 |
} |
|
366 |
} |
|
367 |
|
|
368 |
/**
|
|
369 |
* Build fully qualified cache file for the specified cache entry key.
|
|
370 |
*
|
|
371 |
* @param key Cache Entry Key.
|
|
372 |
* @return File reference.
|
|
373 |
*/
|
|
374 | 653 |
protected File getCacheFile(String key) {
|
375 | 651 |
char[] fileChars = getCacheFileName(key);
|
376 |
|
|
377 | 653 |
File file = new File(root, new String(fileChars) + "." + CACHE_EXTENSION); |
378 |
|
|
379 | 627 |
return file;
|
380 |
} |
|
381 |
|
|
382 |
/**
|
|
383 |
* Build cache file name for the specified cache entry key.
|
|
384 |
*
|
|
385 |
* @param key Cache Entry Key.
|
|
386 |
* @return char[] file name.
|
|
387 |
*/
|
|
388 |
protected abstract char[] getCacheFileName(String key); |
|
389 |
|
|
390 |
/**
|
|
391 |
* Builds a fully qualified file name that specifies a cache group entry.
|
|
392 |
*
|
|
393 |
* @param group The name of the group
|
|
394 |
* @return A File reference
|
|
395 |
*/
|
|
396 | 207 |
private File getCacheGroupFile(String group) {
|
397 | 207 |
int AVERAGE_PATH_LENGTH = 30;
|
398 |
|
|
399 | 207 |
if ((group == null) || (group.length() == 0)) { |
400 | 0 |
throw new IllegalArgumentException("Invalid group '" + group + "' specified to getCacheGroupFile."); |
401 |
} |
|
402 |
|
|
403 | 207 |
StringBuffer path = new StringBuffer(AVERAGE_PATH_LENGTH);
|
404 |
|
|
405 |
// Build a fully qualified file name for this group
|
|
406 | 207 |
path.append(GROUP_DIRECTORY).append('/'); |
407 | 207 |
path.append(group).append('.').append(CACHE_EXTENSION); |
408 |
|
|
409 | 207 |
return new File(root, path.toString()); |
410 |
} |
|
411 |
|
|
412 |
/**
|
|
413 |
* This allows to persist different scopes in different path in the case of
|
|
414 |
* file caching.
|
|
415 |
*
|
|
416 |
* @param scope Cache scope.
|
|
417 |
* @return The scope subpath
|
|
418 |
*/
|
|
419 | 78 |
private String getPathPart(int scope) { |
420 | 78 |
if (scope == PageContext.SESSION_SCOPE) {
|
421 | 0 |
return SESSION_CACHE_SUBPATH;
|
422 |
} else {
|
|
423 | 78 |
return APPLICATION_CACHE_SUBPATH;
|
424 |
} |
|
425 |
} |
|
426 |
|
|
427 |
/**
|
|
428 |
* Clears a whole directory, starting from the specified
|
|
429 |
* directory
|
|
430 |
*
|
|
431 |
* @param baseDirName The root directory to delete
|
|
432 |
* @throws CachePersistenceException
|
|
433 |
*/
|
|
434 | 73 |
private void clear(String baseDirName) throws CachePersistenceException { |
435 | 73 |
File baseDir = new File(baseDirName);
|
436 | 73 |
File[] fileList = baseDir.listFiles(); |
437 |
|
|
438 | 73 |
try {
|
439 | 73 |
if (fileList != null) { |
440 |
// Loop through all the files and directory to delete them
|
|
441 | 22 |
for (int count = 0; count < fileList.length; count++) { |
442 | 22 |
if (fileList[count].isFile()) {
|
443 | 22 |
fileList[count].delete(); |
444 |
} else {
|
|
445 |
// Make a recursive call to delete the directory
|
|
446 | 0 |
clear(fileList[count].toString()); |
447 | 0 |
fileList[count].delete(); |
448 |
} |
|
449 |
} |
|
450 |
} |
|
451 |
|
|
452 |
// Delete the root directory
|
|
453 | 73 |
baseDir.delete(); |
454 |
} catch (Exception e) {
|
|
455 | 0 |
throw new CachePersistenceException("Unable to clear the cache directory"); |
456 |
} |
|
457 |
} |
|
458 |
|
|
459 |
/**
|
|
460 |
* Retrives a serialized object from the supplied file, or returns
|
|
461 |
* <code>null</code> if the file does not exist.
|
|
462 |
*
|
|
463 |
* @param file The file to deserialize
|
|
464 |
* @return The deserialized object
|
|
465 |
* @throws CachePersistenceException
|
|
466 |
*/
|
|
467 | 491 |
private Object retrieve(File file) throws CachePersistenceException { |
468 | 492 |
Object readContent = null;
|
469 | 492 |
boolean fileExist;
|
470 |
|
|
471 | 491 |
try {
|
472 | 492 |
fileExist = file.exists(); |
473 |
} catch (Exception e) {
|
|
474 | 0 |
throw new CachePersistenceException("Unable to verify if " + file + " exists: " + e); |
475 |
} |
|
476 |
|
|
477 |
// Read the file if it exists
|
|
478 | 494 |
if (fileExist) {
|
479 | 269 |
BufferedInputStream in = null;
|
480 | 269 |
ObjectInputStream oin = null;
|
481 |
|
|
482 | 269 |
try {
|
483 | 269 |
in = new BufferedInputStream(new FileInputStream(file)); |
484 | 269 |
oin = new ObjectInputStream(in);
|
485 | 269 |
readContent = oin.readObject(); |
486 |
} catch (Exception e) {
|
|
487 |
// We expect this exception to occur.
|
|
488 |
// This is when the item will be invalidated (written or deleted)
|
|
489 |
// during read.
|
|
490 |
// The cache has the logic to retry reading.
|
|
491 | 0 |
throw new CachePersistenceException("Unable to read '" + file.getAbsolutePath() + "' from the cache: " + e); |
492 |
} finally {
|
|
493 | 269 |
try {
|
494 | 269 |
oin.close(); |
495 |
} catch (Exception ex) {
|
|
496 |
} |
|
497 |
|
|
498 | 269 |
try {
|
499 | 269 |
in.close(); |
500 |
} catch (Exception ex) {
|
|
501 |
} |
|
502 |
} |
|
503 |
} |
|
504 |
|
|
505 | 494 |
return readContent;
|
506 |
} |
|
507 |
} |
|
508 |
|
|