1
16
17 package net.sf.ehcache;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.RandomAccessFile;
22 import java.nio.channels.FileChannel;
23 import java.nio.channels.FileLock;
24 import java.nio.channels.OverlappingFileLockException;
25 import java.util.HashSet;
26 import java.util.Set;
27
28 import net.sf.ehcache.config.DiskStoreConfiguration;
29
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33
39 public final class DiskStorePathManager {
40
46 private static final String AUTO_DISK_PATH_DIRECTORY_PREFIX = "ehcache_auto_created";
47 private static final Logger LOG = LoggerFactory.getLogger(DiskStorePathManager.class);
48 private static final String LOCK_FILE_NAME = ".ehcache-diskstore.lock";
49
50 private static final int DEL = 0x7F;
51 private static final char ESCAPE = '%';
52 private static final Set<Character> ILLEGALS = new HashSet<Character>();
53
54 private final File initialPath;
55 private final boolean defaultPath;
56
57 private volatile DiskStorePath path;
58
59 static {
60 ILLEGALS.add('/');
61 ILLEGALS.add('\\');
62 ILLEGALS.add('<');
63 ILLEGALS.add('>');
64 ILLEGALS.add(':');
65 ILLEGALS.add('"');
66 ILLEGALS.add('|');
67 ILLEGALS.add('?');
68 ILLEGALS.add('*');
69 ILLEGALS.add('.');
70 }
71
72
77 public DiskStorePathManager(String initialPath) {
78 this.initialPath = new File(initialPath);
79 this.defaultPath = false;
80 }
81
82
85 public DiskStorePathManager() {
86 this.initialPath = new File(DiskStoreConfiguration.getDefaultPath());
87 this.defaultPath = true;
88 }
89
90
96 public boolean resolveAndLockIfExists(String file) {
97 if (path != null) {
98 return getFile(file).exists();
99 }
100
101 synchronized (this) {
102 if (path != null) {
103 return getFile(file).exists();
104 }
105
106
107 if (!initialPath.isDirectory()) {
108 return false;
109 } else if (!new File(initialPath, file).exists()) {
110 return false;
111 } else {
112 try {
113 path = new DiskStorePath(initialPath, false, defaultPath);
114 } catch (DiskstoreNotExclusiveException e) {
115 throw new CacheException(e);
116 }
117
118 LOG.debug("Using diskstore path {}", path.getDiskStorePath());
119 LOG.debug("Holding exclusive lock on {}", path.getLockFile());
120 return true;
121 }
122 }
123 }
124
125 private void resolveAndLockIfNeeded(boolean allowAutoCreate) throws DiskstoreNotExclusiveException {
126 if (path != null) {
127 return;
128 }
129
130 synchronized (this) {
131 if (path != null) {
132 return;
133 }
134
135 File candidate = initialPath;
136
137 boolean autoCreated = false;
138 while (true) {
139
140 if (!candidate.isDirectory() && !candidate.mkdirs()) {
141 throw new CacheException("Disk store path can't be created: " + candidate);
142 }
143
144 try {
145 path = new DiskStorePath(candidate, autoCreated, !autoCreated && defaultPath);
146 break;
147 } catch (DiskstoreNotExclusiveException e) {
148 if (!allowAutoCreate) { throw e; }
149
150 autoCreated = true;
151 try {
152 candidate = File.createTempFile(AUTO_DISK_PATH_DIRECTORY_PREFIX, "diskstore", initialPath);
153
154 candidate.delete();
155 } catch (IOException ioe) {
156 throw new CacheException(ioe);
157 }
158 }
159 }
160
161 if (autoCreated) {
162 LOG.warn("diskStorePath '" + initialPath
163 + "' is already used by an existing CacheManager either in the same VM or in a different process.\n"
164 + "The diskStore path for this CacheManager will be set to " + candidate + ".\nTo avoid this"
165 + " warning consider using the CacheManager factory methods to create a singleton CacheManager "
166 + "or specifying a separate ehcache configuration (ehcache.xml) for each CacheManager instance.");
167 }
168
169 LOG.debug("Using diskstore path {}", path.getDiskStorePath());
170 LOG.debug("Holding exclusive lock on {}", path.getLockFile());
171 }
172 }
173
174
180 private static String safeName(String name) {
181 int len = name.length();
182 StringBuilder sb = new StringBuilder(len);
183 for (int i = 0; i < len; i++) {
184 char c = name.charAt(i);
185 if (c <= ' ' || c >= DEL || (c >= 'A' && c <= 'Z') || ILLEGALS.contains(c) || c == ESCAPE) {
186 sb.append(ESCAPE);
187 sb.append(String.format("%04x", (int) c));
188 } else {
189 sb.append(c);
190 }
191 }
192 return sb.toString();
193 }
194
195 private static void deleteFile(File f) {
196 if (!f.delete()) {
197 LOG.debug("Failed to delete file {}", f.getAbsolutePath());
198 }
199 }
200
201
206 public boolean isAutoCreated() {
207 DiskStorePath diskStorePath = path;
208 if (diskStorePath == null) {
209 throw new IllegalStateException();
210 }
211
212 return diskStorePath.isAutoCreated();
213 }
214
215
220 public boolean isDefault() {
221 DiskStorePath diskStorePath = path;
222 if (diskStorePath == null) {
223 throw new IllegalStateException();
224 }
225
226 return diskStorePath.isDefault();
227 }
228
229
233 public synchronized void releaseLock() {
234 try {
235 if (path != null) {
236 path.unlock();
237 }
238 } finally {
239 path = null;
240 }
241 }
242
243
250 public File getFile(String cacheName, String suffix) {
251 return getFile(safeName(cacheName) + suffix);
252 }
253
254
260 public File getFile(String name) {
261 try {
262 resolveAndLockIfNeeded(true);
263 } catch (DiskstoreNotExclusiveException e) {
264 throw new CacheException(e);
265 }
266
267 File diskStorePath = path.getDiskStorePath();
268
269 File file = new File(diskStorePath, name);
270 for (File parent = file.getParentFile(); parent != null; parent = parent.getParentFile()) {
271 if (diskStorePath.equals(parent)) {
272 return file;
273 }
274 }
275
276 throw new IllegalArgumentException("Attempted to access file outside the disk-store path");
277 }
278
279
283 private static class DiskstoreNotExclusiveException extends Exception {
284
285
288 public DiskstoreNotExclusiveException() {
289 super();
290 }
291
292
297 public DiskstoreNotExclusiveException(String message) {
298 super(message);
299 }
300 }
301
302
305 private static class DiskStorePath {
306 private final FileLock directoryLock;
307 private final File lockFile;
308 private final File diskStorePath;
309 private final boolean autoCreated;
310 private final boolean defaultPath;
311
312 DiskStorePath(File path, boolean autoCreated, boolean defaultPath) throws DiskstoreNotExclusiveException {
313 this.diskStorePath = path;
314 this.autoCreated = autoCreated;
315 this.defaultPath = defaultPath;
316
317 lockFile = new File(path.getAbsoluteFile(), LOCK_FILE_NAME);
318 lockFile.deleteOnExit();
319
320 FileLock dirLock;
321 try {
322 lockFile.createNewFile();
323 if (!lockFile.exists()) {
324 throw new AssertionError("Failed to create lock file " + lockFile);
325 }
326 FileChannel lockFileChannel = new RandomAccessFile(lockFile, "rw").getChannel();
327 dirLock = lockFileChannel.tryLock();
328 } catch (OverlappingFileLockException ofle) {
329 dirLock = null;
330 } catch (IOException ioe) {
331 throw new CacheException(ioe);
332 }
333
334 if (dirLock == null) {
335 throw new DiskstoreNotExclusiveException(path.getAbsolutePath() + " is not exclusive.");
336 }
337
338 this.directoryLock = dirLock;
339 }
340
341 boolean isAutoCreated() {
342 return autoCreated;
343 }
344
345 boolean isDefault() {
346 return defaultPath;
347 }
348
349 File getDiskStorePath() {
350 return diskStorePath;
351 }
352
353 File getLockFile() {
354 return lockFile;
355 }
356
357 void unlock() {
358 if (directoryLock != null && directoryLock.isValid()) {
359 try {
360 directoryLock.release();
361 directoryLock.channel().close();
362 deleteFile(lockFile);
363 } catch (IOException e) {
364 throw new CacheException("Failed to release disk store path's lock file:" + lockFile, e);
365 }
366 }
367
368
369 if (autoCreated) {
370 if (diskStorePath.delete()) {
371 LOG.debug("Deleted directory " + diskStorePath.getName());
372 }
373 }
374 }
375
376 @Override
377 public String toString() {
378 return diskStorePath.getAbsolutePath();
379 }
380 }
381
382 }
383