package dk.kb.yggdrasil.db;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import dk.kb.yggdrasil.exceptions.ArgumentCheck;
import dk.kb.yggdrasil.exceptions.YggdrasilException;
/**
* The StateDatabase persists incoming requests (PreservationRequestState) with a Berkeley DB JE Database
*/
public class StateDatabase {
/** The logger used by this class. */
private static Log log = LogFactory.getLog(StateDatabase.class);
/** The basedir for the database itself. */
private File databaseBaseDir;
/** The subdirectory to the databaseBaseDir, where the database is located. */
private static final String DATABASE_SUBDIR = "DB";
/** The name of the database. */
private static final String DATABASE_NAME = "YGGDRASIL";
/** The name of the class database. */
private static final String CLASS_DATABASE_NAME = "classDb";
/** The Database environment. */
private Environment env;
/** The request Database. */
private Database requestDB;
/** The class Database. */
private Database classDB;
/** The Berkeley DB binder for the data object and keyObject in our database,
* i.e. PreservationRequestState. */
private EntryBinding objectBinding;
/** Berkeley DB key binder. */
private EntryBinding keyBinding;
/**
* Constructor.
* Initializes the Berkeley DB databases.
* @param databasedir The directory where the database should be
* @throws DatabaseException If unable to open the database
*/
public StateDatabase(File databasedir) throws DatabaseException{
ArgumentCheck.checkNotNull(databasedir, "File databasedir");
this.databaseBaseDir = databasedir;
initializeDatabase();
}
/**
* Initialize the Berkeley DB databases.
* @throws DatabaseException If unable to open the databases
*/
private void initializeDatabase() throws DatabaseException {
File homeDirectory = new File(databaseBaseDir, DATABASE_SUBDIR);
if (!homeDirectory.isDirectory()) {
boolean success = homeDirectory.mkdirs();
log.warn("The database directory '" + homeDirectory.getAbsolutePath()
+ "' does not exist. Successfully created it: " + success);
}
ArgumentCheck.checkExistsDirectory(homeDirectory, "File homeDirectory");
log.info("Opening DB-environment in: " + homeDirectory.getAbsolutePath());
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setTransactional(true);
envConfig.setAllowCreate(true);
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setTransactional(true);
dbConfig.setAllowCreate(true);
Transaction nullTransaction = null;
env = new Environment(homeDirectory, envConfig);
requestDB = env.openDatabase(nullTransaction, DATABASE_NAME, dbConfig);
// Open the database that stores your class information.
classDB = env.openDatabase(nullTransaction, CLASS_DATABASE_NAME, dbConfig);
StoredClassCatalog classCatalog = new StoredClassCatalog(classDB);
// Create the binding
objectBinding = new SerialBinding(classCatalog, PreservationRequestState.class);
keyBinding = new SerialBinding(classCatalog, String.class);
}
/**
* Retrieve a PreservationRequestState with the given uuid.
* @param uuid A given UUID representing an element in Valhal
* @return a PreservationRequestState with the given uuid
* @throws YggdrasilException If it fails to retrieve the record.
*/
public PreservationRequestState getPreservationRecord(String uuid) throws YggdrasilException {
ArgumentCheck.checkNotNullOrEmpty(uuid, "String uuid");
Transaction nullTransaction = null;
LockMode nullLockMode = null;
DatabaseEntry key = new DatabaseEntry();
keyBinding.objectToEntry(uuid, key);
DatabaseEntry data = new DatabaseEntry();
OperationStatus status = null;
try {
status = requestDB.get(nullTransaction, key, data, nullLockMode);
} catch (DatabaseException e) {
throw new YggdrasilException(
"Could not retrieve the PreservationRequestState for the record '" + uuid, e);
}
PreservationRequestState retrievedRequest = null;
if (status == OperationStatus.SUCCESS) {
retrievedRequest = (PreservationRequestState) objectBinding.entryToObject(data);
}
return retrievedRequest;
}
/**
* Determine, if the database contains a PreservationRequest for a specific uuid.
* @param uuid A given UUID representing an element in Valhal
* @return true, if database contains a PreservationRequest for the given uuid; otherwise false
* @throws YggdrasilException If it fails.
*/
public boolean hasPreservationEntry(String uuid) throws YggdrasilException {
ArgumentCheck.checkNotNullOrEmpty(uuid, "String uuid");
return (getPreservationRecord(uuid) != null);
}
/**
* Create a new PreservationRequest in the database.
* @param uuid A given UUID representing an element in Valhal
* @param request The preservation request state to put.
* @throws YggdrasilException If it fails.
*/
public void putPreservationRecord(String uuid, PreservationRequestState request) throws YggdrasilException {
ArgumentCheck.checkNotNullOrEmpty(uuid, "String uuid");
ArgumentCheck.checkNotNull(request, "PreservationRequestState request");
Transaction txn = env.beginTransaction(null, null);
DatabaseEntry theKey = new DatabaseEntry();
DatabaseEntry theData = new DatabaseEntry();
keyBinding.objectToEntry(uuid, theKey);
objectBinding.objectToEntry(request, theData);
try {
requestDB.put(txn, theKey, theData);
txn.commit();
} catch (DatabaseException e) {
if (txn != null) {
// Roll-back
txn.abort();
txn = null;
}
throw new YggdrasilException("Database exception occuring during ingest", e);
}
}
/**
* Retrieve list of outstanding requests.
* TODO maybe change to retrieve the requests themselves as a list?
* @return The list of UUIDs for the outstanding requests.
* @throws YggdrasilException If it fails to extract the outstanding UUIDs.
*/
public List<String> getOutstandingUUIDS() throws YggdrasilException {
Cursor cursor = null;
CursorConfig nullCursorConfig = null;
Transaction nullTransaction = null;
List<String> resultList = new ArrayList<String>();
try {
cursor = requestDB.openCursor(nullTransaction, nullCursorConfig);
DatabaseEntry foundKey = new DatabaseEntry();
DatabaseEntry foundData = new DatabaseEntry();
while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) ==
OperationStatus.SUCCESS) {
String keyString = (String) keyBinding.entryToObject(foundKey);
resultList.add(keyString);
}
} catch (DatabaseException de) {
throw new YggdrasilException("Error when iterating the PreservationRequestStates ", de);
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (DatabaseException e) {
log.warn("Database error occurred when closing the cursor: ", e);
}
}
}
return resultList;
}
/**
* Delete the entry in the request database with the given uuid.
* @param uuid A given UUID representing an element in Valhal
* @throws YggdrasilException If it is not possible to remove the record.
*/
public void delete(String uuid) throws YggdrasilException {
ArgumentCheck.checkNotNullOrEmpty(uuid, "String uuid");
Transaction txn = env.beginTransaction(null, null);
DatabaseEntry key = new DatabaseEntry();
keyBinding.objectToEntry(uuid, key);
try {
requestDB.delete(txn, key);
txn.commit();
} catch (DatabaseException e) {
if (txn != null) {
// Roll-back
txn.abort();
txn = null;
}
throw new YggdrasilException(
"Database exception occuring during deletion of record", e);
}
}
/**
* Close the databases and set the instance to null.
*/
public void cleanup() {
if (requestDB != null) {
try {
requestDB.close();
} catch (DatabaseException e) {
log.warn("Unable to close request database. The error was :", e);
}
}
if (classDB != null) {
try {
classDB.close();
} catch (DatabaseException e) {
log.warn("Unable to close class database. The error was :", e);
}
}
}
}