4826 |
20 Mar 09 |
nicklas |
1 |
/** |
4826 |
20 Mar 09 |
nicklas |
$Id$ |
4826 |
20 Mar 09 |
nicklas |
3 |
|
4826 |
20 Mar 09 |
nicklas |
Copyright (C) 2009 Nicklas Nordborg |
4826 |
20 Mar 09 |
nicklas |
5 |
|
4826 |
20 Mar 09 |
nicklas |
This file is part of BASE - BioArray Software Environment. |
4826 |
20 Mar 09 |
nicklas |
Available at http://base.thep.lu.se/ |
4826 |
20 Mar 09 |
nicklas |
8 |
|
4826 |
20 Mar 09 |
nicklas |
BASE is free software; you can redistribute it and/or |
4826 |
20 Mar 09 |
nicklas |
modify it under the terms of the GNU General Public License |
4826 |
20 Mar 09 |
nicklas |
as published by the Free Software Foundation; either version 3 |
4826 |
20 Mar 09 |
nicklas |
of the License, or (at your option) any later version. |
4826 |
20 Mar 09 |
nicklas |
13 |
|
4826 |
20 Mar 09 |
nicklas |
BASE is distributed in the hope that it will be useful, |
4826 |
20 Mar 09 |
nicklas |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
4826 |
20 Mar 09 |
nicklas |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4826 |
20 Mar 09 |
nicklas |
GNU General Public License for more details. |
4826 |
20 Mar 09 |
nicklas |
18 |
|
4826 |
20 Mar 09 |
nicklas |
You should have received a copy of the GNU General Public License |
4826 |
20 Mar 09 |
nicklas |
along with BASE. If not, see <http://www.gnu.org/licenses/>. |
4826 |
20 Mar 09 |
nicklas |
21 |
*/ |
4826 |
20 Mar 09 |
nicklas |
22 |
package net.sf.basedb.util; |
4826 |
20 Mar 09 |
nicklas |
23 |
|
7713 |
21 May 19 |
nicklas |
24 |
import java.io.Closeable; |
4826 |
20 Mar 09 |
nicklas |
25 |
import java.io.File; |
4827 |
23 Mar 09 |
nicklas |
26 |
import java.io.FileFilter; |
4826 |
20 Mar 09 |
nicklas |
27 |
import java.io.FileInputStream; |
5384 |
13 Aug 10 |
nicklas |
28 |
import java.io.FileNotFoundException; |
4826 |
20 Mar 09 |
nicklas |
29 |
import java.io.FileOutputStream; |
4827 |
23 Mar 09 |
nicklas |
30 |
import java.io.FilterInputStream; |
4827 |
23 Mar 09 |
nicklas |
31 |
import java.io.FilterOutputStream; |
4826 |
20 Mar 09 |
nicklas |
32 |
import java.io.IOException; |
4826 |
20 Mar 09 |
nicklas |
33 |
import java.io.InputStream; |
4826 |
20 Mar 09 |
nicklas |
34 |
import java.io.ObjectInputStream; |
4826 |
20 Mar 09 |
nicklas |
35 |
import java.io.ObjectOutputStream; |
7324 |
06 Apr 17 |
nicklas |
36 |
import java.io.ObjectStreamClass; |
4826 |
20 Mar 09 |
nicklas |
37 |
import java.io.OutputStream; |
4826 |
20 Mar 09 |
nicklas |
38 |
import java.io.Serializable; |
4827 |
23 Mar 09 |
nicklas |
39 |
import java.lang.ref.WeakReference; |
7713 |
21 May 19 |
nicklas |
40 |
import java.lang.ref.Cleaner.Cleanable; |
4827 |
23 Mar 09 |
nicklas |
41 |
import java.util.List; |
4827 |
23 Mar 09 |
nicklas |
42 |
import java.util.Map; |
4827 |
23 Mar 09 |
nicklas |
43 |
import java.util.TimerTask; |
4827 |
23 Mar 09 |
nicklas |
44 |
import java.util.WeakHashMap; |
4827 |
23 Mar 09 |
nicklas |
45 |
import java.util.concurrent.TimeUnit; |
4827 |
23 Mar 09 |
nicklas |
46 |
import java.util.concurrent.locks.Lock; |
4827 |
23 Mar 09 |
nicklas |
47 |
import java.util.concurrent.locks.ReadWriteLock; |
4827 |
23 Mar 09 |
nicklas |
48 |
import java.util.concurrent.locks.ReentrantReadWriteLock; |
4827 |
23 Mar 09 |
nicklas |
49 |
import java.util.regex.Pattern; |
4826 |
20 Mar 09 |
nicklas |
50 |
|
7715 |
22 May 19 |
nicklas |
51 |
import net.sf.basedb.core.Application; |
7141 |
22 Apr 16 |
nicklas |
52 |
import net.sf.basedb.core.BaseException; |
4827 |
23 Mar 09 |
nicklas |
53 |
import net.sf.basedb.core.InvalidDataException; |
4827 |
23 Mar 09 |
nicklas |
54 |
|
4826 |
20 Mar 09 |
nicklas |
55 |
/** |
4826 |
20 Mar 09 |
nicklas |
A cache for storing data to files on the file system. The typical |
4826 |
20 Mar 09 |
nicklas |
use case is to store data this is expensive to get from the database |
4826 |
20 Mar 09 |
nicklas |
in a file for later retrieval. The cache can be used in streaming mode |
4827 |
23 Mar 09 |
nicklas |
with the any of the {@link #read(String, int)} or {@link #write(String, InputStream, int)} |
4826 |
20 Mar 09 |
nicklas |
methods and their variants. It can also be used to store any {@link Serializable} |
4829 |
23 Mar 09 |
nicklas |
object with {@link #store(String, Object, int)} and {@link #load(String, int)}. |
4826 |
20 Mar 09 |
nicklas |
<p> |
4827 |
23 Mar 09 |
nicklas |
63 |
|
4826 |
20 Mar 09 |
nicklas |
In all cases the cached entry is identified by a key which is more or |
4826 |
20 Mar 09 |
nicklas |
less directly translated to directories on the file system. |
4826 |
20 Mar 09 |
nicklas |
<p> |
4826 |
20 Mar 09 |
nicklas |
67 |
|
4827 |
23 Mar 09 |
nicklas |
This class is thread-safe and can be used by multiple threads at the same |
4827 |
23 Mar 09 |
nicklas |
time. Write requests to the same entry in the cache are allowed to one |
4827 |
23 Mar 09 |
nicklas |
thread at a time. Any number of threads may read from the same cache entry |
4827 |
23 Mar 09 |
nicklas |
as long as no thread is writing to that entry. |
4826 |
20 Mar 09 |
nicklas |
72 |
|
4826 |
20 Mar 09 |
nicklas |
@author Nicklas |
4826 |
20 Mar 09 |
nicklas |
@version 2.11 |
4826 |
20 Mar 09 |
nicklas |
@base.modified $Date$ |
4826 |
20 Mar 09 |
nicklas |
76 |
*/ |
4826 |
20 Mar 09 |
nicklas |
77 |
public class StaticCache |
4826 |
20 Mar 09 |
nicklas |
78 |
{ |
4827 |
23 Mar 09 |
nicklas |
79 |
|
4827 |
23 Mar 09 |
nicklas |
80 |
public static final Pattern validKey = Pattern.compile("[\\w\\/\\.\\-]+"); |
4827 |
23 Mar 09 |
nicklas |
81 |
|
4827 |
23 Mar 09 |
nicklas |
82 |
/** |
4827 |
23 Mar 09 |
nicklas |
Log static cache events. |
4827 |
23 Mar 09 |
nicklas |
84 |
*/ |
6444 |
09 Apr 14 |
nicklas |
85 |
private static final org.slf4j.Logger log = |
6444 |
09 Apr 14 |
nicklas |
86 |
org.slf4j.LoggerFactory.getLogger(StaticCache.class); |
4827 |
23 Mar 09 |
nicklas |
87 |
|
4827 |
23 Mar 09 |
nicklas |
88 |
/** |
4827 |
23 Mar 09 |
nicklas |
Checks if the given key is a vilid cache entry key. |
4827 |
23 Mar 09 |
nicklas |
Allowed characters are: any alphanumerical character + ./- |
4827 |
23 Mar 09 |
nicklas |
@param key The key to check |
4827 |
23 Mar 09 |
nicklas |
@return TRUE if the key is valid |
4827 |
23 Mar 09 |
nicklas |
93 |
*/ |
4827 |
23 Mar 09 |
nicklas |
94 |
public static boolean isValidKey(String key) |
4827 |
23 Mar 09 |
nicklas |
95 |
{ |
7141 |
22 Apr 16 |
nicklas |
96 |
return validKey.matcher(key).matches() && !key.contains("../"); |
4827 |
23 Mar 09 |
nicklas |
97 |
} |
4827 |
23 Mar 09 |
nicklas |
98 |
|
4976 |
18 Jun 09 |
nicklas |
99 |
/** |
4976 |
18 Jun 09 |
nicklas |
Convert a possible invalid cache key to a valid one by |
4976 |
18 Jun 09 |
nicklas |
replacing invalid characters with the replacement string. |
4976 |
18 Jun 09 |
nicklas |
@param key The (possible invalid) cache key |
4976 |
18 Jun 09 |
nicklas |
@param replacement The replacement string |
4976 |
18 Jun 09 |
nicklas |
@return A valid cache key |
4976 |
18 Jun 09 |
nicklas |
@since 2.13 |
4976 |
18 Jun 09 |
nicklas |
106 |
*/ |
4976 |
18 Jun 09 |
nicklas |
107 |
public static String makeValidKey(String key, String replacement) |
4976 |
18 Jun 09 |
nicklas |
108 |
{ |
4976 |
18 Jun 09 |
nicklas |
109 |
Pattern invalid = Pattern.compile("[^\\w\\/\\.\\-]"); |
7141 |
22 Apr 16 |
nicklas |
110 |
return invalid.matcher(key).replaceAll(replacement).replace("../", replacement); |
4976 |
18 Jun 09 |
nicklas |
111 |
} |
4976 |
18 Jun 09 |
nicklas |
112 |
|
4826 |
20 Mar 09 |
nicklas |
113 |
private final File root; |
7141 |
22 Apr 16 |
nicklas |
114 |
private final String rootPath; |
4827 |
23 Mar 09 |
nicklas |
115 |
private final Map<String, LockEntry> locks; |
4826 |
20 Mar 09 |
nicklas |
116 |
private boolean disabled; |
4826 |
20 Mar 09 |
nicklas |
117 |
|
4827 |
23 Mar 09 |
nicklas |
118 |
|
4826 |
20 Mar 09 |
nicklas |
119 |
/** |
4826 |
20 Mar 09 |
nicklas |
Creates a new static cache. |
4826 |
20 Mar 09 |
nicklas |
@param root A directory were the cache stores it's files |
4826 |
20 Mar 09 |
nicklas |
122 |
*/ |
4826 |
20 Mar 09 |
nicklas |
123 |
public StaticCache(File root) |
4826 |
20 Mar 09 |
nicklas |
124 |
{ |
4826 |
20 Mar 09 |
nicklas |
125 |
this.root = root; |
7141 |
22 Apr 16 |
nicklas |
126 |
try |
7141 |
22 Apr 16 |
nicklas |
127 |
{ |
7141 |
22 Apr 16 |
nicklas |
128 |
this.rootPath = root.getCanonicalPath(); |
7141 |
22 Apr 16 |
nicklas |
129 |
} |
7141 |
22 Apr 16 |
nicklas |
130 |
catch (IOException ex) |
7141 |
22 Apr 16 |
nicklas |
131 |
{ |
7141 |
22 Apr 16 |
nicklas |
132 |
throw new BaseException(ex); |
7141 |
22 Apr 16 |
nicklas |
133 |
} |
4827 |
23 Mar 09 |
nicklas |
134 |
this.locks = new WeakHashMap<String, LockEntry>(); |
4827 |
23 Mar 09 |
nicklas |
135 |
log.info("Creating static cache in directory " + root); |
4826 |
20 Mar 09 |
nicklas |
136 |
} |
4826 |
20 Mar 09 |
nicklas |
137 |
|
4826 |
20 Mar 09 |
nicklas |
138 |
/** |
4826 |
20 Mar 09 |
nicklas |
Check if the cache has been disabled. A disabled cache ignores |
4826 |
20 Mar 09 |
nicklas |
all calls to read or write data. |
4826 |
20 Mar 09 |
nicklas |
@return A boolean that is TRUE if the cache is disabled |
4826 |
20 Mar 09 |
nicklas |
142 |
*/ |
4826 |
20 Mar 09 |
nicklas |
143 |
public boolean isDisabled() |
4826 |
20 Mar 09 |
nicklas |
144 |
{ |
4826 |
20 Mar 09 |
nicklas |
145 |
return disabled; |
4826 |
20 Mar 09 |
nicklas |
146 |
} |
4826 |
20 Mar 09 |
nicklas |
147 |
|
4826 |
20 Mar 09 |
nicklas |
148 |
/** |
4826 |
20 Mar 09 |
nicklas |
Disable or enable the cache. |
4826 |
20 Mar 09 |
nicklas |
@param disabled TRUE if the cache should be disabled |
4826 |
20 Mar 09 |
nicklas |
151 |
*/ |
4826 |
20 Mar 09 |
nicklas |
152 |
public void setDisabled(boolean disabled) |
4826 |
20 Mar 09 |
nicklas |
153 |
{ |
4827 |
23 Mar 09 |
nicklas |
154 |
if (disabled) |
4827 |
23 Mar 09 |
nicklas |
155 |
{ |
4827 |
23 Mar 09 |
nicklas |
156 |
log.info("Disabling static cache in directory " + root); |
4827 |
23 Mar 09 |
nicklas |
157 |
} |
4827 |
23 Mar 09 |
nicklas |
158 |
else |
4827 |
23 Mar 09 |
nicklas |
159 |
{ |
4827 |
23 Mar 09 |
nicklas |
160 |
log.info("Enabling static cache in directory " + root); |
4827 |
23 Mar 09 |
nicklas |
161 |
} |
4826 |
20 Mar 09 |
nicklas |
162 |
this.disabled = disabled; |
4826 |
20 Mar 09 |
nicklas |
163 |
} |
4826 |
20 Mar 09 |
nicklas |
164 |
|
4826 |
20 Mar 09 |
nicklas |
165 |
/** |
4827 |
23 Mar 09 |
nicklas |
Remove all files that matches the specified filter from |
4827 |
23 Mar 09 |
nicklas |
the cache. This method is synchronized and can only |
4827 |
23 Mar 09 |
nicklas |
be executed by one thread at a time. |
4827 |
23 Mar 09 |
nicklas |
169 |
|
4827 |
23 Mar 09 |
nicklas |
@param filter A file filter that matches the files to |
4827 |
23 Mar 09 |
nicklas |
remove |
4827 |
23 Mar 09 |
nicklas |
172 |
*/ |
4827 |
23 Mar 09 |
nicklas |
173 |
public synchronized void cleanUp(FileFilter filter) |
4827 |
23 Mar 09 |
nicklas |
174 |
{ |
4827 |
23 Mar 09 |
nicklas |
175 |
log.info("Cleaning up static cache: " + root); |
4827 |
23 Mar 09 |
nicklas |
176 |
List<File> oldFiles = FileUtil.findFiles(root, filter); |
4827 |
23 Mar 09 |
nicklas |
177 |
if (oldFiles == null) return; |
4827 |
23 Mar 09 |
nicklas |
178 |
|
4827 |
23 Mar 09 |
nicklas |
179 |
log.debug("Found " + oldFiles.size() + " files that should be deleted"); |
4827 |
23 Mar 09 |
nicklas |
180 |
int numDeleted = 0; |
4827 |
23 Mar 09 |
nicklas |
181 |
for (File f : oldFiles) |
4827 |
23 Mar 09 |
nicklas |
182 |
{ |
4827 |
23 Mar 09 |
nicklas |
183 |
if (f.delete()) |
4827 |
23 Mar 09 |
nicklas |
184 |
{ |
4827 |
23 Mar 09 |
nicklas |
185 |
log.debug("Removed cached file: " + f); |
4827 |
23 Mar 09 |
nicklas |
186 |
numDeleted++; |
4827 |
23 Mar 09 |
nicklas |
187 |
} |
4827 |
23 Mar 09 |
nicklas |
188 |
else |
4827 |
23 Mar 09 |
nicklas |
189 |
{ |
4827 |
23 Mar 09 |
nicklas |
190 |
log.warn("Failed to remove cached file: " + f); |
4827 |
23 Mar 09 |
nicklas |
191 |
} |
4827 |
23 Mar 09 |
nicklas |
192 |
} |
4977 |
23 Jun 09 |
nicklas |
193 |
int numDirectories = FileUtil.deleteEmptyDirectories(root, false); |
4977 |
23 Jun 09 |
nicklas |
194 |
log.info("Removed " + numDeleted + " files and " + numDirectories + |
4977 |
23 Jun 09 |
nicklas |
195 |
" directories from the static cache: " + root); |
4827 |
23 Mar 09 |
nicklas |
196 |
} |
4827 |
23 Mar 09 |
nicklas |
197 |
|
4827 |
23 Mar 09 |
nicklas |
198 |
/** |
4827 |
23 Mar 09 |
nicklas |
Creates a file filter that matches all files that |
4827 |
23 Mar 09 |
nicklas |
are older than the specified age. |
4827 |
23 Mar 09 |
nicklas |
@param age The age in milliseconds |
4827 |
23 Mar 09 |
nicklas |
@return A file filter |
4827 |
23 Mar 09 |
nicklas |
@see OlderThanFileFilter |
4827 |
23 Mar 09 |
nicklas |
204 |
*/ |
4827 |
23 Mar 09 |
nicklas |
205 |
public FileFilter olderThan(long age) |
4827 |
23 Mar 09 |
nicklas |
206 |
{ |
4827 |
23 Mar 09 |
nicklas |
207 |
return new OlderThanFileFilter(age, true); |
4827 |
23 Mar 09 |
nicklas |
208 |
} |
4827 |
23 Mar 09 |
nicklas |
209 |
|
4827 |
23 Mar 09 |
nicklas |
210 |
/** |
4827 |
23 Mar 09 |
nicklas |
Creates a task that cleans up this cache when it is |
4827 |
23 Mar 09 |
nicklas |
executed. |
4827 |
23 Mar 09 |
nicklas |
@param filter The file filter that determines |
4827 |
23 Mar 09 |
nicklas |
which file that should be deleted |
4827 |
23 Mar 09 |
nicklas |
@return A TimerTask object |
4827 |
23 Mar 09 |
nicklas |
@see #cleanUp(FileFilter) |
4827 |
23 Mar 09 |
nicklas |
217 |
*/ |
4827 |
23 Mar 09 |
nicklas |
218 |
public TimerTask cleanUpTask(FileFilter filter) |
4827 |
23 Mar 09 |
nicklas |
219 |
{ |
4827 |
23 Mar 09 |
nicklas |
220 |
return new CleanupTask(this, filter); |
4827 |
23 Mar 09 |
nicklas |
221 |
} |
4827 |
23 Mar 09 |
nicklas |
222 |
|
4827 |
23 Mar 09 |
nicklas |
223 |
/** |
4976 |
18 Jun 09 |
nicklas |
Checks if a cache entry exists or not. |
4976 |
18 Jun 09 |
nicklas |
@param key The cache key |
4976 |
18 Jun 09 |
nicklas |
@return TRUE if an extry exists, FALSE otherwise |
4976 |
18 Jun 09 |
nicklas |
@since 2.13 |
4976 |
18 Jun 09 |
nicklas |
228 |
*/ |
4976 |
18 Jun 09 |
nicklas |
229 |
public boolean exists(String key) |
4976 |
18 Jun 09 |
nicklas |
230 |
{ |
4976 |
18 Jun 09 |
nicklas |
231 |
validateKey(key); |
7141 |
22 Apr 16 |
nicklas |
232 |
File f = fileFromKey(key); |
4976 |
18 Jun 09 |
nicklas |
233 |
return f.exists(); |
4976 |
18 Jun 09 |
nicklas |
234 |
} |
4976 |
18 Jun 09 |
nicklas |
235 |
|
4976 |
18 Jun 09 |
nicklas |
236 |
/** |
4976 |
18 Jun 09 |
nicklas |
Checks the size (in bytes) of a cache entry. |
4976 |
18 Jun 09 |
nicklas |
@param key The cache key |
4976 |
18 Jun 09 |
nicklas |
@return The size in bytes, or -1 if the cache entry doesn't exists |
4976 |
18 Jun 09 |
nicklas |
@since 2.13 |
4976 |
18 Jun 09 |
nicklas |
241 |
*/ |
4976 |
18 Jun 09 |
nicklas |
242 |
public long size(String key) |
4976 |
18 Jun 09 |
nicklas |
243 |
{ |
4976 |
18 Jun 09 |
nicklas |
244 |
validateKey(key); |
7141 |
22 Apr 16 |
nicklas |
245 |
File f = fileFromKey(key); |
4976 |
18 Jun 09 |
nicklas |
246 |
return f.exists() ? f.length() : -1; |
4976 |
18 Jun 09 |
nicklas |
247 |
} |
4976 |
18 Jun 09 |
nicklas |
248 |
|
4976 |
18 Jun 09 |
nicklas |
249 |
/** |
4827 |
23 Mar 09 |
nicklas |
Store binary information in the cache. If the entry already exists, |
4826 |
20 Mar 09 |
nicklas |
it is overwritten. If the entry is locked by other threads, |
4827 |
23 Mar 09 |
nicklas |
this thread waits the specified number of milliseconds before |
4827 |
23 Mar 09 |
nicklas |
returning. |
4826 |
20 Mar 09 |
nicklas |
254 |
|
4826 |
20 Mar 09 |
nicklas |
@param key The cache key |
4826 |
20 Mar 09 |
nicklas |
@param in An input stream to read from |
4827 |
23 Mar 09 |
nicklas |
@param timeout A timeout in milliseconds to wait for a write lock |
4827 |
23 Mar 09 |
nicklas |
on the requested cache entry |
4826 |
20 Mar 09 |
nicklas |
@return The number of bytes written |
4826 |
20 Mar 09 |
nicklas |
260 |
*/ |
4827 |
23 Mar 09 |
nicklas |
261 |
public long write(String key, InputStream in, int timeout) |
4826 |
20 Mar 09 |
nicklas |
262 |
throws IOException |
4826 |
20 Mar 09 |
nicklas |
263 |
{ |
4826 |
20 Mar 09 |
nicklas |
264 |
if (disabled) return 0; |
4827 |
23 Mar 09 |
nicklas |
265 |
long numBytes = 0; |
4827 |
23 Mar 09 |
nicklas |
266 |
OutputStream out = null; |
4827 |
23 Mar 09 |
nicklas |
267 |
try |
4827 |
23 Mar 09 |
nicklas |
268 |
{ |
4827 |
23 Mar 09 |
nicklas |
269 |
out = getOutputStream(key, timeout); |
4827 |
23 Mar 09 |
nicklas |
270 |
if (out != null) |
4827 |
23 Mar 09 |
nicklas |
271 |
{ |
4827 |
23 Mar 09 |
nicklas |
272 |
numBytes = FileUtil.copy(in, out); |
4827 |
23 Mar 09 |
nicklas |
273 |
out.flush(); |
4827 |
23 Mar 09 |
nicklas |
274 |
} |
4827 |
23 Mar 09 |
nicklas |
275 |
} |
4827 |
23 Mar 09 |
nicklas |
276 |
finally |
4827 |
23 Mar 09 |
nicklas |
277 |
{ |
7713 |
21 May 19 |
nicklas |
278 |
FileUtil.close(out); |
4827 |
23 Mar 09 |
nicklas |
279 |
} |
4826 |
20 Mar 09 |
nicklas |
280 |
return numBytes; |
4826 |
20 Mar 09 |
nicklas |
281 |
} |
4826 |
20 Mar 09 |
nicklas |
282 |
|
4827 |
23 Mar 09 |
nicklas |
283 |
/** |
4827 |
23 Mar 09 |
nicklas |
Get an output stream that can be used to write binary information |
4827 |
23 Mar 09 |
nicklas |
to the cache. If the entry already exists, it is overwritten. |
4827 |
23 Mar 09 |
nicklas |
If the entry is locked by other threads, this thread waits the |
4827 |
23 Mar 09 |
nicklas |
specified number of milliseconds before giving up. |
4827 |
23 Mar 09 |
nicklas |
<p> |
4827 |
23 Mar 09 |
nicklas |
NOTE! It is very important that the caller closes the |
4827 |
23 Mar 09 |
nicklas |
output stream as soon as all data has been written to it. |
4827 |
23 Mar 09 |
nicklas |
Failure to do so may result in locking the cache entry from |
4827 |
23 Mar 09 |
nicklas |
reading by other threads. |
4827 |
23 Mar 09 |
nicklas |
293 |
|
4827 |
23 Mar 09 |
nicklas |
@param key The cache key |
4827 |
23 Mar 09 |
nicklas |
@param timeout A timeout in milliseconds to wait for a write lock |
4827 |
23 Mar 09 |
nicklas |
on the requested cache entry |
4827 |
23 Mar 09 |
nicklas |
@return An output stream, or null if a write lock could not |
4827 |
23 Mar 09 |
nicklas |
be aquired |
4827 |
23 Mar 09 |
nicklas |
299 |
*/ |
4827 |
23 Mar 09 |
nicklas |
300 |
public OutputStream write(String key, int timeout) |
4826 |
20 Mar 09 |
nicklas |
301 |
throws IOException |
4826 |
20 Mar 09 |
nicklas |
302 |
{ |
4826 |
20 Mar 09 |
nicklas |
303 |
if (disabled) return null; |
4827 |
23 Mar 09 |
nicklas |
304 |
return getOutputStream(key, timeout); |
4826 |
20 Mar 09 |
nicklas |
305 |
} |
4826 |
20 Mar 09 |
nicklas |
306 |
|
4827 |
23 Mar 09 |
nicklas |
307 |
/** |
4827 |
23 Mar 09 |
nicklas |
Read binary information from the cache. The contents of the |
4827 |
23 Mar 09 |
nicklas |
specified cache entry will be copied to the specified output |
4827 |
23 Mar 09 |
nicklas |
stream. |
4827 |
23 Mar 09 |
nicklas |
311 |
|
4827 |
23 Mar 09 |
nicklas |
@param key The cache key |
4827 |
23 Mar 09 |
nicklas |
@param out An output stream to write the cache contents to |
4827 |
23 Mar 09 |
nicklas |
@param timeout A timeout in milliseconds to wait for a |
4827 |
23 Mar 09 |
nicklas |
read lock on the requested cache entry |
4827 |
23 Mar 09 |
nicklas |
@return The number of bytes copied |
4827 |
23 Mar 09 |
nicklas |
317 |
*/ |
4827 |
23 Mar 09 |
nicklas |
318 |
public long read(String key, OutputStream out, int timeout) |
4826 |
20 Mar 09 |
nicklas |
319 |
throws IOException |
4826 |
20 Mar 09 |
nicklas |
320 |
{ |
4826 |
20 Mar 09 |
nicklas |
321 |
if (disabled) return 0; |
4826 |
20 Mar 09 |
nicklas |
322 |
long numBytes = 0; |
4827 |
23 Mar 09 |
nicklas |
323 |
InputStream in = null; |
4827 |
23 Mar 09 |
nicklas |
324 |
try |
4827 |
23 Mar 09 |
nicklas |
325 |
{ |
4827 |
23 Mar 09 |
nicklas |
326 |
in = getInputStream(key, timeout); |
4827 |
23 Mar 09 |
nicklas |
327 |
if (in != null) |
4827 |
23 Mar 09 |
nicklas |
328 |
{ |
4827 |
23 Mar 09 |
nicklas |
329 |
numBytes = FileUtil.copy(in, out); |
4827 |
23 Mar 09 |
nicklas |
330 |
} |
4827 |
23 Mar 09 |
nicklas |
331 |
} |
4827 |
23 Mar 09 |
nicklas |
332 |
finally |
4827 |
23 Mar 09 |
nicklas |
333 |
{ |
7713 |
21 May 19 |
nicklas |
334 |
FileUtil.close(in); |
4827 |
23 Mar 09 |
nicklas |
335 |
} |
4826 |
20 Mar 09 |
nicklas |
336 |
return numBytes; |
4826 |
20 Mar 09 |
nicklas |
337 |
} |
4826 |
20 Mar 09 |
nicklas |
338 |
|
4827 |
23 Mar 09 |
nicklas |
339 |
/** |
4827 |
23 Mar 09 |
nicklas |
Get an input stream for reading binary information from the cache. |
4827 |
23 Mar 09 |
nicklas |
341 |
|
4827 |
23 Mar 09 |
nicklas |
@param key The cache key |
4827 |
23 Mar 09 |
nicklas |
@param timeout A timeout in milliseconds to wait for a |
4827 |
23 Mar 09 |
nicklas |
read lock on the requested cache entry |
4827 |
23 Mar 09 |
nicklas |
@return An input stream or null if the cache entry doesn't exists |
4827 |
23 Mar 09 |
nicklas |
or if a read lock could not be aquired |
4827 |
23 Mar 09 |
nicklas |
347 |
*/ |
4827 |
23 Mar 09 |
nicklas |
348 |
public InputStream read(String key, int timeout) |
4826 |
20 Mar 09 |
nicklas |
349 |
throws IOException |
4826 |
20 Mar 09 |
nicklas |
350 |
{ |
4826 |
20 Mar 09 |
nicklas |
351 |
if (disabled) return null; |
4827 |
23 Mar 09 |
nicklas |
352 |
return getInputStream(key, timeout); |
4826 |
20 Mar 09 |
nicklas |
353 |
} |
4826 |
20 Mar 09 |
nicklas |
354 |
|
4827 |
23 Mar 09 |
nicklas |
355 |
/** |
4827 |
23 Mar 09 |
nicklas |
Store a serializable object in the cache. If the entry already exists, |
4827 |
23 Mar 09 |
nicklas |
it is overwritten. If the entry is locked by other threads, |
4827 |
23 Mar 09 |
nicklas |
this thread waits the specified number of milliseconds before |
4827 |
23 Mar 09 |
nicklas |
returning. |
4827 |
23 Mar 09 |
nicklas |
360 |
|
4827 |
23 Mar 09 |
nicklas |
@param key The cache key |
4829 |
23 Mar 09 |
nicklas |
@param object The object to store in the cache, it must be a |
4829 |
23 Mar 09 |
nicklas |
{@link Serializable} object |
4827 |
23 Mar 09 |
nicklas |
@param timeout A timeout in milliseconds to wait for a write lock |
4827 |
23 Mar 09 |
nicklas |
on the requested cache entry |
4827 |
23 Mar 09 |
nicklas |
@return TRUE if the object could be stored, FALSE otherwise |
4827 |
23 Mar 09 |
nicklas |
367 |
*/ |
4829 |
23 Mar 09 |
nicklas |
368 |
public boolean store(String key, Object object, int timeout) |
4826 |
20 Mar 09 |
nicklas |
369 |
{ |
4826 |
20 Mar 09 |
nicklas |
370 |
if (disabled) return false; |
4827 |
23 Mar 09 |
nicklas |
371 |
OutputStream out = null; |
4829 |
23 Mar 09 |
nicklas |
372 |
boolean stored = false; |
4827 |
23 Mar 09 |
nicklas |
373 |
try |
4827 |
23 Mar 09 |
nicklas |
374 |
{ |
4827 |
23 Mar 09 |
nicklas |
375 |
out = getOutputStream(key, timeout); |
4827 |
23 Mar 09 |
nicklas |
376 |
if (out != null) |
4827 |
23 Mar 09 |
nicklas |
377 |
{ |
4827 |
23 Mar 09 |
nicklas |
378 |
ObjectOutputStream oos = new ObjectOutputStream(out); |
4827 |
23 Mar 09 |
nicklas |
379 |
oos.writeObject(object); |
4827 |
23 Mar 09 |
nicklas |
380 |
oos.flush(); |
4829 |
23 Mar 09 |
nicklas |
381 |
stored = true; |
4827 |
23 Mar 09 |
nicklas |
382 |
} |
4827 |
23 Mar 09 |
nicklas |
383 |
} |
4829 |
23 Mar 09 |
nicklas |
384 |
catch (IOException ex) |
4829 |
23 Mar 09 |
nicklas |
385 |
{ |
4829 |
23 Mar 09 |
nicklas |
386 |
log.warn("Could not store cached entry: " + key, ex); |
4829 |
23 Mar 09 |
nicklas |
387 |
} |
4827 |
23 Mar 09 |
nicklas |
388 |
finally |
4827 |
23 Mar 09 |
nicklas |
389 |
{ |
7713 |
21 May 19 |
nicklas |
390 |
FileUtil.close(out); |
4827 |
23 Mar 09 |
nicklas |
391 |
} |
4829 |
23 Mar 09 |
nicklas |
392 |
return stored; |
4826 |
20 Mar 09 |
nicklas |
393 |
} |
4826 |
20 Mar 09 |
nicklas |
394 |
|
4827 |
23 Mar 09 |
nicklas |
395 |
/** |
4827 |
23 Mar 09 |
nicklas |
Read a serializable object from the cache. |
4827 |
23 Mar 09 |
nicklas |
397 |
|
4827 |
23 Mar 09 |
nicklas |
@param key The cache key |
4827 |
23 Mar 09 |
nicklas |
@param timeout A timeout in milliseconds to wait for a read lock |
4827 |
23 Mar 09 |
nicklas |
on the requested cache entry |
4827 |
23 Mar 09 |
nicklas |
@return The materialized object, or null if the cache entry doesn't |
4827 |
23 Mar 09 |
nicklas |
exists or if a read lock couldn't be aquired |
4827 |
23 Mar 09 |
nicklas |
403 |
*/ |
7610 |
27 Feb 19 |
nicklas |
404 |
public <T> T load(String key, int timeout) |
4826 |
20 Mar 09 |
nicklas |
405 |
{ |
7324 |
06 Apr 17 |
nicklas |
406 |
return load(key, null, timeout); |
7324 |
06 Apr 17 |
nicklas |
407 |
} |
7324 |
06 Apr 17 |
nicklas |
408 |
|
7610 |
27 Feb 19 |
nicklas |
409 |
@SuppressWarnings("unchecked") |
7610 |
27 Feb 19 |
nicklas |
410 |
public <T> T load(String key, ClassLoader loader, int timeout) |
7324 |
06 Apr 17 |
nicklas |
411 |
{ |
4826 |
20 Mar 09 |
nicklas |
412 |
if (disabled) return null; |
4826 |
20 Mar 09 |
nicklas |
413 |
Serializable object = null; |
4827 |
23 Mar 09 |
nicklas |
414 |
InputStream in = null; |
4827 |
23 Mar 09 |
nicklas |
415 |
try |
4826 |
20 Mar 09 |
nicklas |
416 |
{ |
4827 |
23 Mar 09 |
nicklas |
417 |
in = getInputStream(key, timeout); |
4827 |
23 Mar 09 |
nicklas |
418 |
if (in != null) |
4826 |
20 Mar 09 |
nicklas |
419 |
{ |
7324 |
06 Apr 17 |
nicklas |
// Create stream and override resolveClass so we |
7324 |
06 Apr 17 |
nicklas |
// can load classes from the given class loader |
7324 |
06 Apr 17 |
nicklas |
422 |
ObjectInputStream oin = new ObjectInputStream(in) |
7324 |
06 Apr 17 |
nicklas |
423 |
{ |
7324 |
06 Apr 17 |
nicklas |
424 |
@Override |
7324 |
06 Apr 17 |
nicklas |
425 |
protected Class<?> resolveClass(ObjectStreamClass streamClass) |
7324 |
06 Apr 17 |
nicklas |
426 |
throws IOException, ClassNotFoundException |
7324 |
06 Apr 17 |
nicklas |
427 |
{ |
7324 |
06 Apr 17 |
nicklas |
428 |
Class<?> c = null; |
7324 |
06 Apr 17 |
nicklas |
429 |
try |
7324 |
06 Apr 17 |
nicklas |
430 |
{ |
7324 |
06 Apr 17 |
nicklas |
431 |
c = super.resolveClass(streamClass); |
7324 |
06 Apr 17 |
nicklas |
432 |
} |
7324 |
06 Apr 17 |
nicklas |
433 |
catch (ClassNotFoundException ex) |
7324 |
06 Apr 17 |
nicklas |
434 |
{ |
7324 |
06 Apr 17 |
nicklas |
435 |
if (loader == null) throw ex; |
7324 |
06 Apr 17 |
nicklas |
436 |
c = Class.forName(streamClass.getName(), false, loader); |
7324 |
06 Apr 17 |
nicklas |
437 |
} |
7324 |
06 Apr 17 |
nicklas |
438 |
return c; |
7324 |
06 Apr 17 |
nicklas |
439 |
} |
7324 |
06 Apr 17 |
nicklas |
440 |
}; |
4827 |
23 Mar 09 |
nicklas |
441 |
try |
4827 |
23 Mar 09 |
nicklas |
442 |
{ |
4827 |
23 Mar 09 |
nicklas |
443 |
object = (Serializable)oin.readObject(); |
4827 |
23 Mar 09 |
nicklas |
444 |
} |
4827 |
23 Mar 09 |
nicklas |
445 |
catch (ClassNotFoundException ex) |
4827 |
23 Mar 09 |
nicklas |
446 |
{ |
4827 |
23 Mar 09 |
nicklas |
447 |
throw new IOException(ex); |
4827 |
23 Mar 09 |
nicklas |
448 |
} |
4826 |
20 Mar 09 |
nicklas |
449 |
} |
4826 |
20 Mar 09 |
nicklas |
450 |
} |
4829 |
23 Mar 09 |
nicklas |
451 |
catch (IOException ex) |
4829 |
23 Mar 09 |
nicklas |
452 |
{ |
4829 |
23 Mar 09 |
nicklas |
453 |
log.warn("Could not load cached entry: " + key, ex); |
4829 |
23 Mar 09 |
nicklas |
454 |
} |
4827 |
23 Mar 09 |
nicklas |
455 |
finally |
4827 |
23 Mar 09 |
nicklas |
456 |
{ |
7713 |
21 May 19 |
nicklas |
457 |
FileUtil.close(in); |
4827 |
23 Mar 09 |
nicklas |
458 |
} |
7610 |
27 Feb 19 |
nicklas |
459 |
return (T)object; |
7324 |
06 Apr 17 |
nicklas |
460 |
|
4826 |
20 Mar 09 |
nicklas |
461 |
} |
4826 |
20 Mar 09 |
nicklas |
462 |
|
5121 |
09 Oct 09 |
nicklas |
463 |
/** |
5121 |
09 Oct 09 |
nicklas |
Deletes an object from the cache. |
5121 |
09 Oct 09 |
nicklas |
465 |
|
5121 |
09 Oct 09 |
nicklas |
@param key The cache key |
5121 |
09 Oct 09 |
nicklas |
@param timeout A timeout in milliseconds to wait for a write lock |
5121 |
09 Oct 09 |
nicklas |
on the requested cache entry |
5121 |
09 Oct 09 |
nicklas |
@return TRUE if the cached object was deleted or if it didn't exist to |
5121 |
09 Oct 09 |
nicklas |
begin with or if the static cache is disabled, FALSE is only returned |
5121 |
09 Oct 09 |
nicklas |
if it is certain that the cached object still exists after this call |
5121 |
09 Oct 09 |
nicklas |
@since 2.14 |
5121 |
09 Oct 09 |
nicklas |
473 |
*/ |
5121 |
09 Oct 09 |
nicklas |
474 |
public boolean delete(String key, int timeout) |
5121 |
09 Oct 09 |
nicklas |
475 |
{ |
5121 |
09 Oct 09 |
nicklas |
476 |
if (disabled) return true; |
5121 |
09 Oct 09 |
nicklas |
477 |
validateKey(key); |
5121 |
09 Oct 09 |
nicklas |
478 |
log.debug("Delete request for static cache: " + key); |
7141 |
22 Apr 16 |
nicklas |
479 |
File f = fileFromKey(key); |
5121 |
09 Oct 09 |
nicklas |
480 |
if (!f.exists()) |
5121 |
09 Oct 09 |
nicklas |
481 |
{ |
5121 |
09 Oct 09 |
nicklas |
482 |
log.debug("Cache entry doesn't exist: " + key); |
5121 |
09 Oct 09 |
nicklas |
483 |
return true; |
5121 |
09 Oct 09 |
nicklas |
484 |
} |
5121 |
09 Oct 09 |
nicklas |
485 |
LockEntry lock = aquireLock(key, true, timeout); |
5121 |
09 Oct 09 |
nicklas |
486 |
if (lock == null) return false; |
5121 |
09 Oct 09 |
nicklas |
487 |
try |
5121 |
09 Oct 09 |
nicklas |
488 |
{ |
5121 |
09 Oct 09 |
nicklas |
489 |
return f.delete(); |
5121 |
09 Oct 09 |
nicklas |
490 |
} |
5121 |
09 Oct 09 |
nicklas |
491 |
finally |
5121 |
09 Oct 09 |
nicklas |
492 |
{ |
5121 |
09 Oct 09 |
nicklas |
493 |
lock.writeLock().unlock(); |
5121 |
09 Oct 09 |
nicklas |
494 |
} |
5121 |
09 Oct 09 |
nicklas |
495 |
} |
5121 |
09 Oct 09 |
nicklas |
496 |
|
4826 |
20 Mar 09 |
nicklas |
497 |
private void validateKey(String key) |
4826 |
20 Mar 09 |
nicklas |
498 |
{ |
4827 |
23 Mar 09 |
nicklas |
499 |
if (!isValidKey(key)) |
4827 |
23 Mar 09 |
nicklas |
500 |
{ |
4827 |
23 Mar 09 |
nicklas |
501 |
throw new InvalidDataException("Invalid cache key: " + key); |
4827 |
23 Mar 09 |
nicklas |
502 |
} |
4826 |
20 Mar 09 |
nicklas |
503 |
} |
4826 |
20 Mar 09 |
nicklas |
504 |
|
7141 |
22 Apr 16 |
nicklas |
505 |
private File fileFromKey(String key) |
7141 |
22 Apr 16 |
nicklas |
506 |
{ |
7141 |
22 Apr 16 |
nicklas |
507 |
File f = new File(root, key); |
7141 |
22 Apr 16 |
nicklas |
// The key must result in a file path that is a child to the root path! |
7141 |
22 Apr 16 |
nicklas |
509 |
try |
7141 |
22 Apr 16 |
nicklas |
510 |
{ |
7141 |
22 Apr 16 |
nicklas |
511 |
String keyPath = f.getCanonicalPath(); |
7141 |
22 Apr 16 |
nicklas |
512 |
if (!keyPath.startsWith(rootPath)) |
7141 |
22 Apr 16 |
nicklas |
513 |
{ |
7141 |
22 Apr 16 |
nicklas |
514 |
throw new InvalidDataException("Invalid path to cache entry: "+ keyPath); |
7141 |
22 Apr 16 |
nicklas |
515 |
} |
7141 |
22 Apr 16 |
nicklas |
516 |
} |
7141 |
22 Apr 16 |
nicklas |
517 |
catch (IOException ex) |
7141 |
22 Apr 16 |
nicklas |
518 |
{ |
7141 |
22 Apr 16 |
nicklas |
519 |
throw new InvalidDataException("Invalid cache key: "+ key, ex); |
7141 |
22 Apr 16 |
nicklas |
520 |
} |
7141 |
22 Apr 16 |
nicklas |
521 |
return f; |
7141 |
22 Apr 16 |
nicklas |
522 |
} |
7141 |
22 Apr 16 |
nicklas |
523 |
|
4827 |
23 Mar 09 |
nicklas |
524 |
/** |
4827 |
23 Mar 09 |
nicklas |
Get a lock-safe input stream. |
4827 |
23 Mar 09 |
nicklas |
526 |
*/ |
4827 |
23 Mar 09 |
nicklas |
527 |
private InputStream getInputStream(String key, int timeout) |
4826 |
20 Mar 09 |
nicklas |
528 |
throws IOException |
4826 |
20 Mar 09 |
nicklas |
529 |
{ |
4826 |
20 Mar 09 |
nicklas |
530 |
validateKey(key); |
4827 |
23 Mar 09 |
nicklas |
531 |
log.debug("Read request for static cache: " + key); |
7141 |
22 Apr 16 |
nicklas |
532 |
File f = fileFromKey(key); |
4827 |
23 Mar 09 |
nicklas |
533 |
if (!f.exists()) |
4827 |
23 Mar 09 |
nicklas |
534 |
{ |
4827 |
23 Mar 09 |
nicklas |
535 |
log.debug("Cache entry doesn't exist: " + key); |
4827 |
23 Mar 09 |
nicklas |
536 |
return null; |
4827 |
23 Mar 09 |
nicklas |
537 |
} |
4827 |
23 Mar 09 |
nicklas |
538 |
LockEntry lock = aquireLock(key, false, timeout); |
4827 |
23 Mar 09 |
nicklas |
539 |
if (lock == null) return null; |
4827 |
23 Mar 09 |
nicklas |
540 |
InputStream in = null; |
4827 |
23 Mar 09 |
nicklas |
541 |
try |
4827 |
23 Mar 09 |
nicklas |
542 |
{ |
5384 |
13 Aug 10 |
nicklas |
543 |
if (!f.setLastModified(System.currentTimeMillis())) |
5384 |
13 Aug 10 |
nicklas |
544 |
{ |
5384 |
13 Aug 10 |
nicklas |
545 |
log.warn("Failed to set last modification time on file: " + f); |
5384 |
13 Aug 10 |
nicklas |
546 |
} |
4827 |
23 Mar 09 |
nicklas |
547 |
in = new LockSafeInputStream(new FileInputStream(f), lock); |
4827 |
23 Mar 09 |
nicklas |
548 |
} |
4827 |
23 Mar 09 |
nicklas |
549 |
finally |
4827 |
23 Mar 09 |
nicklas |
550 |
{ |
4827 |
23 Mar 09 |
nicklas |
551 |
if (in == null) lock.readLock().unlock(); |
4827 |
23 Mar 09 |
nicklas |
552 |
} |
4827 |
23 Mar 09 |
nicklas |
553 |
return in; |
4826 |
20 Mar 09 |
nicklas |
554 |
} |
4826 |
20 Mar 09 |
nicklas |
555 |
|
4827 |
23 Mar 09 |
nicklas |
556 |
/** |
4827 |
23 Mar 09 |
nicklas |
Get a lock-safe output stream. |
4827 |
23 Mar 09 |
nicklas |
558 |
*/ |
4827 |
23 Mar 09 |
nicklas |
559 |
private OutputStream getOutputStream(String key, int timeout) |
4826 |
20 Mar 09 |
nicklas |
560 |
throws IOException |
4826 |
20 Mar 09 |
nicklas |
561 |
{ |
4826 |
20 Mar 09 |
nicklas |
562 |
validateKey(key); |
4827 |
23 Mar 09 |
nicklas |
563 |
log.debug("Write request for static cache: " + key); |
4827 |
23 Mar 09 |
nicklas |
564 |
LockEntry lock = aquireLock(key, true, timeout); |
4827 |
23 Mar 09 |
nicklas |
565 |
if (lock == null) return null; |
4827 |
23 Mar 09 |
nicklas |
566 |
OutputStream out = null; |
4827 |
23 Mar 09 |
nicklas |
567 |
try |
4827 |
23 Mar 09 |
nicklas |
568 |
{ |
7141 |
22 Apr 16 |
nicklas |
569 |
File f = fileFromKey(key); |
5384 |
13 Aug 10 |
nicklas |
570 |
File dir = f.getParentFile(); |
5384 |
13 Aug 10 |
nicklas |
571 |
if (!dir.mkdirs() && !dir.isDirectory()) |
5384 |
13 Aug 10 |
nicklas |
572 |
{ |
5384 |
13 Aug 10 |
nicklas |
573 |
throw new FileNotFoundException("Could not create directory: " + f.getParentFile()); |
5384 |
13 Aug 10 |
nicklas |
574 |
} |
5384 |
13 Aug 10 |
nicklas |
575 |
if (!f.createNewFile() && !f.exists()) |
5384 |
13 Aug 10 |
nicklas |
576 |
{ |
5384 |
13 Aug 10 |
nicklas |
577 |
throw new FileNotFoundException("Could not create file: " + f); |
5384 |
13 Aug 10 |
nicklas |
578 |
} |
4827 |
23 Mar 09 |
nicklas |
579 |
out = new LockSafeOutputStream(new FileOutputStream(f), lock); |
4827 |
23 Mar 09 |
nicklas |
580 |
} |
4827 |
23 Mar 09 |
nicklas |
581 |
finally |
4827 |
23 Mar 09 |
nicklas |
582 |
{ |
4827 |
23 Mar 09 |
nicklas |
583 |
if (out == null) lock.writeLock().unlock(); |
4827 |
23 Mar 09 |
nicklas |
584 |
} |
4827 |
23 Mar 09 |
nicklas |
585 |
return out; |
4826 |
20 Mar 09 |
nicklas |
586 |
} |
4826 |
20 Mar 09 |
nicklas |
587 |
|
4827 |
23 Mar 09 |
nicklas |
588 |
/** |
4827 |
23 Mar 09 |
nicklas |
Aquire a read or write lock on a given cache entry. |
4827 |
23 Mar 09 |
nicklas |
@return A lock manager or null if the lock could not be aquired |
4827 |
23 Mar 09 |
nicklas |
591 |
*/ |
4827 |
23 Mar 09 |
nicklas |
592 |
private LockEntry aquireLock(String key, boolean writeLock, int timeout) |
4827 |
23 Mar 09 |
nicklas |
593 |
{ |
4827 |
23 Mar 09 |
nicklas |
594 |
String lockType = writeLock ? "write" : "read"; |
4827 |
23 Mar 09 |
nicklas |
595 |
log.debug("Trying to get " + lockType + " lock on: " + key); |
4827 |
23 Mar 09 |
nicklas |
596 |
LockEntry rwLock = null; |
4827 |
23 Mar 09 |
nicklas |
597 |
synchronized (locks) |
4827 |
23 Mar 09 |
nicklas |
598 |
{ |
4827 |
23 Mar 09 |
nicklas |
599 |
log.debug("Number of known lock entries: " + locks.size()); |
4827 |
23 Mar 09 |
nicklas |
600 |
rwLock = locks.get(key); |
4827 |
23 Mar 09 |
nicklas |
601 |
if (rwLock == null) |
4827 |
23 Mar 09 |
nicklas |
602 |
{ |
4827 |
23 Mar 09 |
nicklas |
603 |
log.debug("Create new lock holder for: " + key); |
4827 |
23 Mar 09 |
nicklas |
604 |
rwLock = new LockEntry(new ReentrantReadWriteLock(), key); |
4827 |
23 Mar 09 |
nicklas |
605 |
locks.put(key, rwLock); |
4827 |
23 Mar 09 |
nicklas |
606 |
} |
4827 |
23 Mar 09 |
nicklas |
607 |
} |
4827 |
23 Mar 09 |
nicklas |
608 |
|
4827 |
23 Mar 09 |
nicklas |
609 |
Lock lock = writeLock ? rwLock.writeLock() : rwLock.readLock(); |
4827 |
23 Mar 09 |
nicklas |
610 |
try |
4827 |
23 Mar 09 |
nicklas |
611 |
{ |
4827 |
23 Mar 09 |
nicklas |
612 |
if (!lock.tryLock(timeout, TimeUnit.MILLISECONDS)) |
4827 |
23 Mar 09 |
nicklas |
613 |
{ |
4827 |
23 Mar 09 |
nicklas |
614 |
log.debug("Timeout while waiting for " + lockType + " lock on: " + key); |
4827 |
23 Mar 09 |
nicklas |
615 |
rwLock = null; |
4827 |
23 Mar 09 |
nicklas |
616 |
} |
4827 |
23 Mar 09 |
nicklas |
617 |
} |
4827 |
23 Mar 09 |
nicklas |
618 |
catch (InterruptedException ex) |
4827 |
23 Mar 09 |
nicklas |
619 |
{ |
4827 |
23 Mar 09 |
nicklas |
620 |
log.debug("Interrupted while waiting for " + lockType + " lock on: " + key); |
7352 |
28 Apr 17 |
nicklas |
621 |
Thread.currentThread().interrupt(); |
4827 |
23 Mar 09 |
nicklas |
622 |
rwLock = null; |
4827 |
23 Mar 09 |
nicklas |
623 |
} |
4827 |
23 Mar 09 |
nicklas |
624 |
if (rwLock != null) log.debug("Got " + lockType + " lock on: " + key); |
4827 |
23 Mar 09 |
nicklas |
625 |
return rwLock; |
4827 |
23 Mar 09 |
nicklas |
626 |
} |
4826 |
20 Mar 09 |
nicklas |
627 |
|
4827 |
23 Mar 09 |
nicklas |
628 |
/** |
4827 |
23 Mar 09 |
nicklas |
A lock-safe output stream that releases the associated |
4827 |
23 Mar 09 |
nicklas |
write lock when the stream is closed. |
4827 |
23 Mar 09 |
nicklas |
<p> |
4827 |
23 Mar 09 |
nicklas |
NOTE! It is important that we keep a strong reference |
4827 |
23 Mar 09 |
nicklas |
to the 'key' in this class until the stream is closed, |
4827 |
23 Mar 09 |
nicklas |
since otherwise the locked entry may be reclaimed by the |
4827 |
23 Mar 09 |
nicklas |
garbage collector. |
4827 |
23 Mar 09 |
nicklas |
636 |
*/ |
4827 |
23 Mar 09 |
nicklas |
637 |
static class LockSafeOutputStream |
4827 |
23 Mar 09 |
nicklas |
638 |
extends FilterOutputStream |
4827 |
23 Mar 09 |
nicklas |
639 |
{ |
7713 |
21 May 19 |
nicklas |
640 |
private final State state; |
7713 |
21 May 19 |
nicklas |
641 |
private final Cleanable cleanable; |
7713 |
21 May 19 |
nicklas |
642 |
private final String key; |
4827 |
23 Mar 09 |
nicklas |
643 |
private boolean closed; |
4827 |
23 Mar 09 |
nicklas |
644 |
|
4827 |
23 Mar 09 |
nicklas |
645 |
LockSafeOutputStream(OutputStream out, LockEntry lock) |
4827 |
23 Mar 09 |
nicklas |
646 |
{ |
4827 |
23 Mar 09 |
nicklas |
647 |
super(out); |
7713 |
21 May 19 |
nicklas |
648 |
state = new State(out, lock, false); |
7713 |
21 May 19 |
nicklas |
649 |
state.calledFrom = new Throwable(); |
7715 |
22 May 19 |
nicklas |
650 |
cleanable = Application.registerCleanup(this, state); |
7713 |
21 May 19 |
nicklas |
651 |
key = lock.getKey(); |
4827 |
23 Mar 09 |
nicklas |
652 |
} |
4827 |
23 Mar 09 |
nicklas |
653 |
|
4827 |
23 Mar 09 |
nicklas |
654 |
@Override |
4827 |
23 Mar 09 |
nicklas |
655 |
public void close() |
4827 |
23 Mar 09 |
nicklas |
656 |
throws IOException |
4827 |
23 Mar 09 |
nicklas |
657 |
{ |
4827 |
23 Mar 09 |
nicklas |
658 |
if (closed) return; |
4827 |
23 Mar 09 |
nicklas |
659 |
log.debug("Releasing write lock on: " + key); |
7713 |
21 May 19 |
nicklas |
660 |
state.calledFrom = null; // Set to null to not get a warning in the log file |
7715 |
22 May 19 |
nicklas |
661 |
if (cleanable != null) cleanable.clean(); |
4827 |
23 Mar 09 |
nicklas |
662 |
closed = true; |
4827 |
23 Mar 09 |
nicklas |
663 |
} |
4827 |
23 Mar 09 |
nicklas |
664 |
|
4827 |
23 Mar 09 |
nicklas |
665 |
@Override |
4827 |
23 Mar 09 |
nicklas |
666 |
public String toString() |
4827 |
23 Mar 09 |
nicklas |
667 |
{ |
4827 |
23 Mar 09 |
nicklas |
668 |
return "LockSafeOutputStream[" + key + "]"; |
4827 |
23 Mar 09 |
nicklas |
669 |
} |
4827 |
23 Mar 09 |
nicklas |
670 |
|
4827 |
23 Mar 09 |
nicklas |
671 |
} |
4827 |
23 Mar 09 |
nicklas |
672 |
|
4827 |
23 Mar 09 |
nicklas |
673 |
|
4827 |
23 Mar 09 |
nicklas |
674 |
/** |
4827 |
23 Mar 09 |
nicklas |
A lock-safe input stream that releases the associated |
4827 |
23 Mar 09 |
nicklas |
read lock when the stream is closed. |
4827 |
23 Mar 09 |
nicklas |
<p> |
4827 |
23 Mar 09 |
nicklas |
NOTE! It is important that we keep a strong reference |
4827 |
23 Mar 09 |
nicklas |
to the 'key' in this class until the stream is closed, |
4827 |
23 Mar 09 |
nicklas |
since otherwise the locked entry may be reclaimed by the |
4827 |
23 Mar 09 |
nicklas |
garbage collector. |
4827 |
23 Mar 09 |
nicklas |
682 |
*/ |
4827 |
23 Mar 09 |
nicklas |
683 |
static class LockSafeInputStream |
4827 |
23 Mar 09 |
nicklas |
684 |
extends FilterInputStream |
4827 |
23 Mar 09 |
nicklas |
685 |
{ |
4827 |
23 Mar 09 |
nicklas |
686 |
|
7713 |
21 May 19 |
nicklas |
687 |
private final State state; |
7713 |
21 May 19 |
nicklas |
688 |
private final Cleanable cleanable; |
7713 |
21 May 19 |
nicklas |
689 |
private final String key; |
4827 |
23 Mar 09 |
nicklas |
690 |
private boolean closed; |
4827 |
23 Mar 09 |
nicklas |
691 |
|
4827 |
23 Mar 09 |
nicklas |
692 |
LockSafeInputStream(InputStream in, LockEntry lock) |
4827 |
23 Mar 09 |
nicklas |
693 |
{ |
4827 |
23 Mar 09 |
nicklas |
694 |
super(in); |
7713 |
21 May 19 |
nicklas |
695 |
state = new State(in, lock, true); |
7713 |
21 May 19 |
nicklas |
696 |
state.calledFrom = new Throwable(); |
7715 |
22 May 19 |
nicklas |
697 |
cleanable = Application.registerCleanup(this, state); |
7713 |
21 May 19 |
nicklas |
698 |
key = lock.getKey(); |
4827 |
23 Mar 09 |
nicklas |
699 |
} |
4827 |
23 Mar 09 |
nicklas |
700 |
|
4827 |
23 Mar 09 |
nicklas |
701 |
@Override |
4827 |
23 Mar 09 |
nicklas |
702 |
public void close() |
4827 |
23 Mar 09 |
nicklas |
703 |
throws IOException |
4827 |
23 Mar 09 |
nicklas |
704 |
{ |
4827 |
23 Mar 09 |
nicklas |
705 |
if (closed) return; |
4827 |
23 Mar 09 |
nicklas |
706 |
log.debug("Releasing read lock on: " + key); |
7713 |
21 May 19 |
nicklas |
707 |
state.calledFrom = null; // Set to null to not get a warning in the log file |
7715 |
22 May 19 |
nicklas |
708 |
if (cleanable != null) cleanable.clean(); |
4827 |
23 Mar 09 |
nicklas |
709 |
closed = true; |
4827 |
23 Mar 09 |
nicklas |
710 |
} |
4827 |
23 Mar 09 |
nicklas |
711 |
|
4827 |
23 Mar 09 |
nicklas |
712 |
@Override |
4827 |
23 Mar 09 |
nicklas |
713 |
public String toString() |
4827 |
23 Mar 09 |
nicklas |
714 |
{ |
4827 |
23 Mar 09 |
nicklas |
715 |
return "LockSafeInputStream[" + key + "]"; |
4827 |
23 Mar 09 |
nicklas |
716 |
} |
4827 |
23 Mar 09 |
nicklas |
717 |
} |
4827 |
23 Mar 09 |
nicklas |
718 |
|
4827 |
23 Mar 09 |
nicklas |
719 |
/** |
4827 |
23 Mar 09 |
nicklas |
Keeps track of a locked cached entry. The 'key' is the same key |
4827 |
23 Mar 09 |
nicklas |
instance that was used to create the entry in the first place. |
4827 |
23 Mar 09 |
nicklas |
It is important that all active maintainers of a lock also |
4827 |
23 Mar 09 |
nicklas |
keeps a strong reference to the key since the lock entry may |
4827 |
23 Mar 09 |
nicklas |
otherwise be reclaimed by the garbage collector. |
4827 |
23 Mar 09 |
nicklas |
725 |
*/ |
4827 |
23 Mar 09 |
nicklas |
726 |
static class LockEntry |
4827 |
23 Mar 09 |
nicklas |
727 |
implements ReadWriteLock |
4827 |
23 Mar 09 |
nicklas |
728 |
{ |
4827 |
23 Mar 09 |
nicklas |
729 |
private ReadWriteLock rwLock; |
4827 |
23 Mar 09 |
nicklas |
730 |
private WeakReference<String> keyRef; |
4827 |
23 Mar 09 |
nicklas |
731 |
|
4827 |
23 Mar 09 |
nicklas |
732 |
LockEntry(ReadWriteLock rwLock, String key) |
4827 |
23 Mar 09 |
nicklas |
733 |
{ |
4827 |
23 Mar 09 |
nicklas |
734 |
this.rwLock = rwLock; |
4827 |
23 Mar 09 |
nicklas |
735 |
this.keyRef = new WeakReference<String>(key); |
4827 |
23 Mar 09 |
nicklas |
736 |
} |
4827 |
23 Mar 09 |
nicklas |
737 |
|
4827 |
23 Mar 09 |
nicklas |
738 |
@Override |
4827 |
23 Mar 09 |
nicklas |
739 |
public Lock writeLock() |
4827 |
23 Mar 09 |
nicklas |
740 |
{ |
4827 |
23 Mar 09 |
nicklas |
741 |
return rwLock.writeLock(); |
4827 |
23 Mar 09 |
nicklas |
742 |
} |
4827 |
23 Mar 09 |
nicklas |
743 |
|
4827 |
23 Mar 09 |
nicklas |
744 |
@Override |
4827 |
23 Mar 09 |
nicklas |
745 |
public Lock readLock() |
4827 |
23 Mar 09 |
nicklas |
746 |
{ |
4827 |
23 Mar 09 |
nicklas |
747 |
return rwLock.readLock(); |
4827 |
23 Mar 09 |
nicklas |
748 |
} |
4827 |
23 Mar 09 |
nicklas |
749 |
|
4827 |
23 Mar 09 |
nicklas |
750 |
public String getKey() |
4827 |
23 Mar 09 |
nicklas |
751 |
{ |
4827 |
23 Mar 09 |
nicklas |
752 |
return keyRef.get(); |
4827 |
23 Mar 09 |
nicklas |
753 |
} |
4827 |
23 Mar 09 |
nicklas |
754 |
|
4827 |
23 Mar 09 |
nicklas |
755 |
@Override |
4827 |
23 Mar 09 |
nicklas |
756 |
public String toString() |
4827 |
23 Mar 09 |
nicklas |
757 |
{ |
4827 |
23 Mar 09 |
nicklas |
758 |
return "LockEntry[" + getKey() + "]"; |
4827 |
23 Mar 09 |
nicklas |
759 |
} |
4827 |
23 Mar 09 |
nicklas |
760 |
} |
4827 |
23 Mar 09 |
nicklas |
761 |
|
4827 |
23 Mar 09 |
nicklas |
762 |
/** |
7713 |
21 May 19 |
nicklas |
Inner class for performing the actual release of locks and closing of streams (in/out). |
7713 |
21 May 19 |
nicklas |
764 |
*/ |
7713 |
21 May 19 |
nicklas |
765 |
static class State |
7713 |
21 May 19 |
nicklas |
766 |
implements Runnable |
7713 |
21 May 19 |
nicklas |
767 |
{ |
7713 |
21 May 19 |
nicklas |
768 |
|
7713 |
21 May 19 |
nicklas |
769 |
final Closeable stream; |
7713 |
21 May 19 |
nicklas |
770 |
final LockEntry lock; |
7713 |
21 May 19 |
nicklas |
771 |
final boolean readLock; |
7713 |
21 May 19 |
nicklas |
772 |
Throwable calledFrom; |
7713 |
21 May 19 |
nicklas |
773 |
|
7713 |
21 May 19 |
nicklas |
774 |
State(Closeable stream, LockEntry lock, boolean readLock) |
7713 |
21 May 19 |
nicklas |
775 |
{ |
7713 |
21 May 19 |
nicklas |
776 |
this.stream = stream; |
7713 |
21 May 19 |
nicklas |
777 |
this.lock = lock; |
7713 |
21 May 19 |
nicklas |
778 |
this.readLock = readLock; |
7713 |
21 May 19 |
nicklas |
779 |
} |
7713 |
21 May 19 |
nicklas |
780 |
|
7713 |
21 May 19 |
nicklas |
781 |
@Override |
7713 |
21 May 19 |
nicklas |
782 |
public void run() |
7713 |
21 May 19 |
nicklas |
783 |
{ |
7713 |
21 May 19 |
nicklas |
784 |
if (calledFrom != null) |
7713 |
21 May 19 |
nicklas |
785 |
{ |
7713 |
21 May 19 |
nicklas |
786 |
log.warn("Found unreleased " + (readLock ? "read" : "write") + " lock on: " + lock.getKey(), calledFrom); |
7713 |
21 May 19 |
nicklas |
787 |
} |
7713 |
21 May 19 |
nicklas |
788 |
if (stream != null) FileUtil.close(stream); |
7713 |
21 May 19 |
nicklas |
789 |
if (readLock) |
7713 |
21 May 19 |
nicklas |
790 |
{ |
7713 |
21 May 19 |
nicklas |
791 |
lock.readLock().unlock(); |
7713 |
21 May 19 |
nicklas |
792 |
} |
7713 |
21 May 19 |
nicklas |
793 |
else |
7713 |
21 May 19 |
nicklas |
794 |
{ |
7713 |
21 May 19 |
nicklas |
795 |
lock.writeLock().unlock(); |
7713 |
21 May 19 |
nicklas |
796 |
} |
7713 |
21 May 19 |
nicklas |
797 |
} |
7713 |
21 May 19 |
nicklas |
798 |
} |
7713 |
21 May 19 |
nicklas |
799 |
|
7713 |
21 May 19 |
nicklas |
800 |
|
7713 |
21 May 19 |
nicklas |
801 |
/** |
4827 |
23 Mar 09 |
nicklas |
A timer task that clean up the cache when it is executed. |
4827 |
23 Mar 09 |
nicklas |
803 |
*/ |
5384 |
13 Aug 10 |
nicklas |
804 |
public static class CleanupTask |
4827 |
23 Mar 09 |
nicklas |
805 |
extends TimerTask |
4827 |
23 Mar 09 |
nicklas |
806 |
{ |
4827 |
23 Mar 09 |
nicklas |
807 |
|
4827 |
23 Mar 09 |
nicklas |
808 |
private final StaticCache cache; |
4827 |
23 Mar 09 |
nicklas |
809 |
private final FileFilter filter; |
4827 |
23 Mar 09 |
nicklas |
810 |
|
4827 |
23 Mar 09 |
nicklas |
811 |
public CleanupTask(StaticCache cache, FileFilter filter) |
4827 |
23 Mar 09 |
nicklas |
812 |
{ |
4827 |
23 Mar 09 |
nicklas |
813 |
this.cache = cache; |
4827 |
23 Mar 09 |
nicklas |
814 |
this.filter = filter; |
4827 |
23 Mar 09 |
nicklas |
815 |
} |
4827 |
23 Mar 09 |
nicklas |
816 |
|
4827 |
23 Mar 09 |
nicklas |
817 |
@Override |
4827 |
23 Mar 09 |
nicklas |
818 |
public void run() |
4827 |
23 Mar 09 |
nicklas |
819 |
{ |
4827 |
23 Mar 09 |
nicklas |
820 |
cache.cleanUp(filter); |
4827 |
23 Mar 09 |
nicklas |
821 |
} |
4827 |
23 Mar 09 |
nicklas |
822 |
} |
4826 |
20 Mar 09 |
nicklas |
823 |
} |