package com.thinkaurelius.titan.diskstorage.persistit;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.configuration.Configuration;
import com.persistit.Exchange;
import com.persistit.Persistit;
import com.persistit.Volume;
import com.persistit.exception.PersistitException;
import com.thinkaurelius.titan.diskstorage.PermanentStorageException;
import com.thinkaurelius.titan.diskstorage.StorageException;
import com.thinkaurelius.titan.diskstorage.common.LocalStoreManager;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreFeatures;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTransaction;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTxConfig;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.keyvalue.KVMutation;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.keyvalue.OrderedKeyValueStoreManager;
import com.thinkaurelius.titan.graphdb.configuration.GraphDatabaseConfiguration;
import com.thinkaurelius.titan.util.system.IOUtils;
/**
* @todo: confirm that the initial sessions created on store startup are not hanging around forever
*/
public class PersistitStoreManager extends LocalStoreManager implements OrderedKeyValueStoreManager {
private final Map<String, PersistitKeyValueStore> stores;
final static String VOLUME_NAME = "titan";
final static String BUFFER_COUNT_KEY = "buffercount";
final static Integer BUFFER_COUNT_DEFAULT = 5000;
private final Persistit db;
private final Properties properties;
protected final StoreFeatures features = getDefaultFeatures();
public PersistitStoreManager(Configuration configuration) throws StorageException {
super(configuration);
stores = new HashMap<String, PersistitKeyValueStore>();
// read config and setup
String datapath = configuration.getString(GraphDatabaseConfiguration.STORAGE_DIRECTORY_KEY);
Integer bufferCount = configuration.getInt(BUFFER_COUNT_KEY, BUFFER_COUNT_DEFAULT);
properties = new Properties();
properties.put("datapath", datapath);
// On pathSeparator is ":" on 'Nix systems - File.separator is what is intended.
properties.put("journalpath", directory + File.separator + VOLUME_NAME);
properties.put("logfile", directory + File.separator + VOLUME_NAME + ".log");
// @todo: make these tunable
properties.put("buffer.count.16384", bufferCount.toString());
properties.put("volume.1", directory + File.separator + VOLUME_NAME
+ ",create,pageSize:16384,initialPages:1000,extensionPages:1000,maximumPages:1000000");
try {
db = new Persistit(properties);
db.initialize();
} catch (PersistitException ex) {
throw new PermanentStorageException(ex);
}
}
Volume getVolume() {
return db.getVolume(VOLUME_NAME);
}
@Override
public PersistitKeyValueStore openDatabase(String name) throws StorageException {
if (stores.containsKey(name)) {
return stores.get(name);
}
PersistitTransaction tx = new PersistitTransaction(db, new StoreTxConfig());
PersistitKeyValueStore store = new PersistitKeyValueStore(name, this, db);
tx.commit();
stores.put(name, store);
return store;
}
@Override
public void mutateMany(Map<String, KVMutation> mutations, StoreTransaction txh) throws StorageException {
throw new UnsupportedOperationException();
}
public void removeDatabase(PersistitKeyValueStore db) {
if (!stores.containsKey(db.getName())) {
throw new IllegalArgumentException("Tried to remove an unknown database from the storage manager");
}
stores.remove(db.getName());
}
@Override
public void close() throws StorageException {
if (db != null) {
if (!stores.isEmpty()) {
throw new IllegalStateException("Cannot shutdown manager since some databases are still open");
}
try {
db.close(true);
} catch (PersistitException ex) {
throw new PermanentStorageException(ex);
}
}
}
/**
* Returns a transaction handle for a new transaction.
*
* @return New Transaction Handle
*/
@Override
public PersistitTransaction beginTransaction(final StoreTxConfig config) throws StorageException {
//all Exchanges created by a thread share the same transaction context
return new PersistitTransaction(db, config);
}
@Override
public StoreFeatures getFeatures() {
return features;
}
@Override
public void clearStorage() throws StorageException {
for (String key : stores.keySet()) {
PersistitKeyValueStore store = stores.remove(key);
store.clear();
}
Volume volume;
String[] treeNames;
try {
volume = db.getVolume(VOLUME_NAME);
treeNames = volume.getTreeNames();
} catch (PersistitException ex) {
throw new PermanentStorageException(ex);
}
for (String treeName : treeNames) {
try {
Exchange ex = new Exchange(db, volume, treeName, false);
ex.removeTree();
} catch (PersistitException ex) {
throw new PermanentStorageException(ex);
}
}
close();
IOUtils.deleteFromDirectory(directory);
}
@Override
public String getName() {
return getClass().getSimpleName() + ":" + directory.toString();
}
private StoreFeatures getDefaultFeatures() {
StoreFeatures features = new StoreFeatures();
features.supportsTransactions = true;
features.isDistributed = false;
//@todo: figure out what these do, Copied from Berkeley for now
features.supportsOrderedScan = true;
features.supportsUnorderedScan = false;
features.supportsBatchMutation = false;
features.supportsConsistentKeyOperations = false;
features.supportsLocking = true;
features.isKeyOrdered = true;
features.hasLocalKeyPartition = false;
features.supportsMultiQuery = false;
return features;
}
}