net.sf.ehcache.DiskStorePathManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache-core Show documentation
Show all versions of ehcache-core Show documentation
Internal ehcache-core module. This artifact is not meant to be used directly
/**
* Copyright Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.ehcache;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.HashSet;
import java.util.Set;
import net.sf.ehcache.config.DiskStoreConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manager class to handle disk store path. CacheManager has a reference to this manager.
*
* @author hhuynh
*
*/
public final class DiskStorePathManager {
/**
* If the CacheManager needs to resolve a conflict with the disk path, it will create a
* subdirectory in the given disk path with this prefix followed by a number. The presence of this
* name is used to determined whether it makes sense for a persistent DiskStore to be loaded. Loading
* persistent DiskStores will only have useful semantics where the diskStore path has not changed.
*/
private static final String AUTO_DISK_PATH_DIRECTORY_PREFIX = "ehcache_auto_created";
private static final Logger LOG = LoggerFactory.getLogger(DiskStorePathManager.class);
private static final String LOCK_FILE_NAME = ".ehcache-diskstore.lock";
private static final int DEL = 0x7F;
private static final char ESCAPE = '%';
private static final Set ILLEGALS = new HashSet();
private final File initialPath;
private final boolean defaultPath;
private volatile DiskStorePath path;
static {
ILLEGALS.add('/');
ILLEGALS.add('\\');
ILLEGALS.add('<');
ILLEGALS.add('>');
ILLEGALS.add(':');
ILLEGALS.add('"');
ILLEGALS.add('|');
ILLEGALS.add('?');
ILLEGALS.add('*');
ILLEGALS.add('.');
}
/**
* Create a diskstore path manager with provided initial path.
*
* @param initialPath diskstore path on disk
*/
public DiskStorePathManager(String initialPath) {
this.initialPath = new File(initialPath);
this.defaultPath = false;
}
/**
* Create a diskstore path manager using the default path.
*/
public DiskStorePathManager() {
this.initialPath = new File(DiskStoreConfiguration.getDefaultPath());
this.defaultPath = true;
}
/**
* Resolve and lock this disk store path if the resultant path contains the supplied file.
*
* @param file file to check for
* @return {@code true} if the file existed and the path was successfully locked
*/
public boolean resolveAndLockIfExists(String file) {
if (path != null) {
return getFile(file).exists();
}
synchronized (this) {
if (path != null) {
return getFile(file).exists();
}
// ensure disk store path exists
if (!initialPath.isDirectory()) {
return false;
} else if (!new File(initialPath, file).exists()) {
return false;
} else {
try {
path = new DiskStorePath(initialPath, false, defaultPath);
} catch (DiskstoreNotExclusiveException e) {
throw new CacheException(e);
}
LOG.debug("Using diskstore path {}", path.getDiskStorePath());
LOG.debug("Holding exclusive lock on {}", path.getLockFile());
return true;
}
}
}
private void resolveAndLockIfNeeded(boolean allowAutoCreate) throws DiskstoreNotExclusiveException {
if (path != null) {
return;
}
synchronized (this) {
if (path != null) {
return;
}
File candidate = initialPath;
boolean autoCreated = false;
while (true) {
// ensure disk store path exists
if (!candidate.isDirectory() && !candidate.mkdirs()) {
throw new CacheException("Disk store path can't be created: " + candidate);
}
try {
path = new DiskStorePath(candidate, autoCreated, !autoCreated && defaultPath);
break;
} catch (DiskstoreNotExclusiveException e) {
if (!allowAutoCreate) { throw e; }
autoCreated = true;
try {
candidate = File.createTempFile(AUTO_DISK_PATH_DIRECTORY_PREFIX, "diskstore", initialPath);
// we want to create a directory with this temp name so deleting the file first
candidate.delete();
} catch (IOException ioe) {
throw new CacheException(ioe);
}
}
}
if (autoCreated) {
LOG.warn("diskStorePath '" + initialPath
+ "' is already used by an existing CacheManager either in the same VM or in a different process.\n"
+ "The diskStore path for this CacheManager will be set to " + candidate + ".\nTo avoid this"
+ " warning consider using the CacheManager factory methods to create a singleton CacheManager "
+ "or specifying a separate ehcache configuration (ehcache.xml) for each CacheManager instance.");
}
LOG.debug("Using diskstore path {}", path.getDiskStorePath());
LOG.debug("Holding exclusive lock on {}", path.getLockFile());
}
}
/**
* sanitize a name for valid file or directory name
*
* @param name
* @return sanitized version of name
*/
private static String safeName(String name) {
int len = name.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = name.charAt(i);
if (c <= ' ' || c >= DEL || (c >= 'A' && c <= 'Z') || ILLEGALS.contains(c) || c == ESCAPE) {
sb.append(ESCAPE);
sb.append(String.format("%04x", (int) c));
} else {
sb.append(c);
}
}
return sb.toString();
}
private static void deleteFile(File f) {
if (!f.delete()) {
LOG.debug("Failed to delete file {}", f.getAbsolutePath());
}
}
/**
* Was this path auto-created (ie. the result of a collision)
*
* @return true if path is auto created
*/
public boolean isAutoCreated() {
DiskStorePath diskStorePath = path;
if (diskStorePath == null) {
throw new IllegalStateException();
}
return diskStorePath.isAutoCreated();
}
/**
* Was this path sourced from the default value.
*
* @return true if path is the default
*/
public boolean isDefault() {
DiskStorePath diskStorePath = path;
if (diskStorePath == null) {
throw new IllegalStateException();
}
return diskStorePath.isDefault();
}
/**
* release the lock file used for collision detection
* should be called when cache manager shutdowns
*/
public synchronized void releaseLock() {
try {
if (path != null) {
path.unlock();
}
} finally {
path = null;
}
}
/**
* Get a file object for the given cache-name and suffix
*
* @param cacheName the cache name
* @param suffix a file suffix
* @return a file object
*/
public File getFile(String cacheName, String suffix) {
return getFile(safeName(cacheName) + suffix);
}
/**
* Get a file object for the given name
*
* @param name the file name
* @return a file object
*/
public File getFile(String name) {
try {
resolveAndLockIfNeeded(true);
} catch (DiskstoreNotExclusiveException e) {
throw new CacheException(e);
}
File diskStorePath = path.getDiskStorePath();
File file = new File(diskStorePath, name);
for (File parent = file.getParentFile(); parent != null; parent = parent.getParentFile()) {
if (diskStorePath.equals(parent)) {
return file;
}
}
throw new IllegalArgumentException("Attempted to access file outside the disk-store path");
}
/**
* Exception class thrown when a diskstore path collides with an existing one
*
*/
private static class DiskstoreNotExclusiveException extends Exception {
/**
* Constructor for the DiskstoreNotExclusiveException object.
*/
public DiskstoreNotExclusiveException() {
super();
}
/**
* Constructor for the DiskstoreNotExclusiveException object.
*
* @param message the exception detail message
*/
public DiskstoreNotExclusiveException(String message) {
super(message);
}
}
/**
* Resolved path and lock details
*/
private static class DiskStorePath {
private final FileLock directoryLock;
private final File lockFile;
private final File diskStorePath;
private final boolean autoCreated;
private final boolean defaultPath;
DiskStorePath(File path, boolean autoCreated, boolean defaultPath) throws DiskstoreNotExclusiveException {
this.diskStorePath = path;
this.autoCreated = autoCreated;
this.defaultPath = defaultPath;
lockFile = new File(path.getAbsoluteFile(), LOCK_FILE_NAME);
lockFile.deleteOnExit();
FileLock dirLock;
try {
lockFile.createNewFile();
if (!lockFile.exists()) {
throw new AssertionError("Failed to create lock file " + lockFile);
}
FileChannel lockFileChannel = new RandomAccessFile(lockFile, "rw").getChannel();
dirLock = lockFileChannel.tryLock();
} catch (OverlappingFileLockException ofle) {
dirLock = null;
} catch (IOException ioe) {
throw new CacheException(ioe);
}
if (dirLock == null) {
throw new DiskstoreNotExclusiveException(path.getAbsolutePath() + " is not exclusive.");
}
this.directoryLock = dirLock;
}
boolean isAutoCreated() {
return autoCreated;
}
boolean isDefault() {
return defaultPath;
}
File getDiskStorePath() {
return diskStorePath;
}
File getLockFile() {
return lockFile;
}
void unlock() {
if (directoryLock != null && directoryLock.isValid()) {
try {
directoryLock.release();
directoryLock.channel().close();
deleteFile(lockFile);
} catch (IOException e) {
throw new CacheException("Failed to release disk store path's lock file:" + lockFile, e);
}
}
if (autoCreated) {
if (diskStorePath.delete()) {
LOG.debug("Deleted directory " + diskStorePath.getName());
}
}
}
@Override
public String toString() {
return diskStorePath.getAbsolutePath();
}
}
}