package org.bodytrack.datastore;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.fluxtream.core.aspects.FlxLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* <p>
* <code>FilesystemKeyValueStore</code> is a {@link KeyValueStore} based on the filesystem.
* </p>
* <p>
* Each key corresponds to a file in the filesystem, and thus one value per file. A key names is translated to a file
* path by converting all "<code>.</code>" characters to "<code>/</code>".
* </p>
*
* @author Randy Sargent (rsargent@cmu.edu)
* @author Chris Bartley (bartley@cmu.edu)
*/
public final class FilesystemKeyValueStore implements KeyValueStore<byte[]> {
private static final FlxLogger LOG = FlxLogger.getLogger(FilesystemKeyValueStore.class);
private static final String VALUE_FILE_EXTENSION = ".val";
/**
* Converts the given <code>key</code> to a file, if the key is {@link KeyValueStoreHelper#isValidKey(String) valid}.
* Returns <code>null</code> if the key is invalid.
*
* @throws IllegalArgumentException If <code>rootDirectory</code> and/or <code>key</code> is null
*/
@Nullable
public static File keyToFile(@NotNull final File rootDirectory, @NotNull final String key) throws IllegalArgumentException {
//noinspection ConstantConditions
if (rootDirectory == null || key == null) {
throw new IllegalArgumentException("The rootDirectory and key must both be non-null.");
}
if (KeyValueStoreHelper.isValidKey(key)) {
// convert dots to slashes and append to the root directory to create a new File
final String pathKey = key.replace(".", File.separator);
return new File(rootDirectory, pathKey + VALUE_FILE_EXTENSION);
}
return null;
}
private final File rootDirectory;
/**
* Creates a new <code>FilesystemKeyValueStore</code> with the given <code>rootDirectory</code>. Throws an
* {@link IllegalArgumentException} if the given directory is <code>null</code> or does not exist.
*
* @throws IllegalArgumentException If <code>rootDirectory</code> is null
*/
public FilesystemKeyValueStore(@NotNull final File rootDirectory) throws IllegalArgumentException {
//noinspection ConstantConditions
if (rootDirectory == null || !rootDirectory.exists()) {
throw new IllegalArgumentException("The rootDirectory cannot be null and must exist [" + rootDirectory + "].");
}
this.rootDirectory = rootDirectory;
}
@Override
public boolean hasKey(@Nullable final String key) {
return getValueFile(key) != null;
}
@Override
public boolean set(@NotNull final String key, @NotNull final byte[] value) throws IllegalArgumentException {
//noinspection ConstantConditions
if (key == null || value == null) {
throw new IllegalArgumentException("The key and value must both be non-null.");
}
if (value.length > 0) {
final File valueFile = keyToFile(rootDirectory, key);
if (valueFile != null) {
try {
// make sure the path exists
// noinspection ResultOfMethodCallIgnored
valueFile.getParentFile().mkdirs();
if (valueFile.getParentFile().exists()) {
IOUtils.write(value, new FileOutputStream(valueFile));
return true;
}
else {
LOG.error("FilesystemKeyValueStore.set(): Failed to create the directories for value file [" + valueFile + "]");
}
}
catch (IOException e) {
LOG.error("IOException while trying to write to file [" + valueFile + "]", e);
}
}
}
return false;
}
@Override
@Nullable
public byte[] get(@Nullable final String key) {
final File valueFile = getValueFile(key);
if (valueFile != null) {
try {
return IOUtils.toByteArray(new FileInputStream(valueFile));
}
catch (IOException e) {
LOG.error("IOException while trying to read file [" + valueFile + "]", e);
}
}
return null;
}
@Override
public boolean delete(@Nullable final String key) {
final File valueFile = getValueFile(key);
return valueFile != null && valueFile.delete();
}
/**
* Returns the {@link File} for the given <code>key</code> if and only if the key is non-<code>null</code>, valid
* AND denotes an existing key. Returns <code>null</code> otherwise.
*/
@Nullable
private File getValueFile(@Nullable final String key) {
if (key != null) {
final File valueFile = keyToFile(rootDirectory, key);
// isFile also tests existence for us
if (valueFile != null && valueFile.isFile()) {
return valueFile;
}
}
return null;
}
}