package com.rubiconproject.oss.kv.backends; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.HashMap; import java.util.Map; import com.rubiconproject.oss.kv.BaseManagedKeyValueStore; import com.rubiconproject.oss.kv.KeyValueStoreException; import com.rubiconproject.oss.kv.annotations.Configurable; import com.rubiconproject.oss.kv.annotations.Configurable.Type; import com.rubiconproject.oss.kv.transcoder.SerializableTranscoder; import com.rubiconproject.oss.kv.transcoder.Transcoder; import com.rubiconproject.oss.kv.util.StreamUtils; /** * A key value store that uses the local file system for content. * * @author samtingleff * */ public class FileSystemKeyValueStore extends BaseManagedKeyValueStore { public static final String IDENTIFIER = "filesystem"; private Transcoder defaultTranscoder = new SerializableTranscoder(); private String rootDirectory; private File root; private boolean sanitizeKeys = true; private boolean removeEmptyDirectories = true; public FileSystemKeyValueStore() { } public FileSystemKeyValueStore(String rootDirectory) { this.rootDirectory = rootDirectory; } public FileSystemKeyValueStore(File root) { this.rootDirectory = root.getAbsolutePath(); } @Configurable(name = "root", accepts = Type.StringType) public void setRoot(String rootDirectory) { this.rootDirectory = rootDirectory; } @Configurable(name = "sanitizeKeys", accepts = Type.BooleanType) public void setSanitizeKeys(boolean sanitizeKeys) { this.sanitizeKeys = sanitizeKeys; } @Configurable(name = "cleanEmptyDirectories", accepts = Type.BooleanType) public void setCleanEmptyDirectories(boolean removeEmptyDirectories) { this.removeEmptyDirectories = removeEmptyDirectories; } public String getIdentifier() { return IDENTIFIER; } public void start() throws IOException { root = new File(rootDirectory); if (!root.exists()) { root.mkdirs(); } assert (root.canRead()); super.start(); } public boolean exists(String key) throws KeyValueStoreException, IOException { assertReadable(); File f = getFile(key); return f.exists(); } public Object get(String key) throws KeyValueStoreException, IOException { assertReadable(); return get(key, defaultTranscoder); } public Object get(String key, Transcoder transcoder) throws KeyValueStoreException, IOException { assertReadable(); File f = getFile(key); if (!f.exists()) return null; try { byte[] bytes = StreamUtils.fileToBytes(f); Object obj = transcoder.decode(bytes); return obj; } finally { } } public Map<String, Object> getBulk(String... keys) throws KeyValueStoreException, IOException { assertReadable(); Map<String, Object> results = new HashMap<String, Object>(); for (String key : keys) { Object obj = get(key); if (obj != null) results.put(key, obj); } return results; } public Map<String, Object> getBulk(final List<String> keys) throws KeyValueStoreException, IOException { assertReadable(); Map<String, Object> results = new HashMap<String, Object>(); for (String key : keys) { Object obj = get(key); if (obj != null) results.put(key, obj); } return results; } public Map<String, Object> getBulk(final List<String> keys, Transcoder transcoder) throws KeyValueStoreException, IOException { assertReadable(); Map<String, Object> results = new HashMap<String, Object>(); for (String key : keys) { Object obj = get(key, transcoder); if (obj != null) results.put(key, obj); } return results; } public void set(String key, Object value) throws KeyValueStoreException, IOException { assertWriteable(); set(key, value, defaultTranscoder); } public void set(String key, Object value, Transcoder transcoder) throws KeyValueStoreException, IOException { assertWriteable(); File f = getFile(key); File tempFile = File.createTempFile("temp-file", ".tmp", f .getParentFile()); try { OutputStream os = new FileOutputStream(tempFile); try { byte[] bytes = transcoder.encode(value); os.write(bytes); } finally { os.close(); } tempFile.renameTo(f); } catch (Exception e) { try { tempFile.delete(); } catch (Exception e1) { } } } public void delete(String key) throws KeyValueStoreException, IOException { assertWriteable(); File f = getFile(key); if (f.exists()) { f.delete(); if (removeEmptyDirectories) { // File parent = f.getParentFile(); File current = f.getParentFile(); while ((current.listFiles().length == 0) && (!current.equals(root))) { current.delete(); current = current.getParentFile(); } } } } private File getFile(String key) throws IOException, KeyValueStoreException { if (sanitizeKeys) key = key.replaceAll("[^a-zA-Z0-9\\.\\-_\\+\\/]", "_"); File file = new File(root, key); if (!(file.getCanonicalPath().startsWith(root.getCanonicalPath()))) { throw new KeyValueStoreException("permission denied"); } File parent = file.getParentFile(); if (!parent.exists()) parent.mkdirs(); return file; } }