/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.apiimpl;
import com.github.geophile.erdo.*;
import com.github.geophile.erdo.TransactionRolledBackException;
import com.github.geophile.erdo.forest.Forest;
import com.github.geophile.erdo.map.Factory;
import com.github.geophile.erdo.map.diskmap.DBStructure;
import com.github.geophile.erdo.transaction.*;
import com.github.geophile.erdo.transaction.Transaction;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
public abstract class DatabaseImpl extends Database
{
// Database interface
public static DatabaseOnDisk createDatabase(File dbDirectory,
Configuration configuration,
Class<? extends Factory> factoryClass)
throws IOException, InterruptedException
{
setPID();
synchronized (STATIC_LOCK) {
checkDatabaseClosed();
return DatabaseOnDisk.createDatabase(dbDirectory, factory(factoryClass, configuration));
}
}
public static DatabaseOnDisk useDatabase(File dbDirectory,
Configuration configuration,
Class<? extends Factory> factoryClass)
throws IOException, InterruptedException
{
setPID();
synchronized (STATIC_LOCK) {
checkDatabaseClosed();
return DatabaseOnDisk.openDatabase
(dbDirectory, factory(factoryClass, mergedConfiguration(dbDirectory, configuration)));
}
}
// synchronized to prevent race condition when two threads create maps with the same name at the
// same time.
@Override
public synchronized OrderedMap createMap(String mapName, RecordFactory recordFactory)
throws UsageError, IOException, InterruptedException
{
LOG.log(Level.INFO, "Creating OrderedMap {0}", mapName);
checkDatabaseOpen();
if (maps.containsKey(mapName)) {
throw new UsageError(String.format(
"Cannot create map %s because it already exists", mapName));
}
OrderedMapImpl map = new OrderedMapImpl(this);
factory.registerRecordFactory(map.erdoId(), recordFactory);
maps.put(mapName, map);
return map;
}
// synchronized so that two threads opening the same map at the same time get the same OrderedMap object.
@Override
public synchronized OrderedMap useMap(String mapName)
throws UsageError, IOException, InterruptedException
{
checkDatabaseOpen();
OrderedMapImpl map = maps.get(mapName);
if (map == null) {
throw new UsageError(String.format("Map %s does not exist", mapName));
}
return map;
}
@Override
public void lock(AbstractKey key)
throws InterruptedException,
com.github.geophile.erdo.transaction.DeadlockException,
TransactionRolledBackException
{
forest.lock(key);
}
@Override
public final void commitTransactionAsynchronously(TransactionCallback transactionCallback, Object commitInfo)
throws IOException, InterruptedException
{
checkDatabaseOpen();
forest.commitTransaction(transactionCallback, commitInfo);
}
@Override
public final void rollbackTransaction() throws IOException, InterruptedException
{
checkDatabaseOpen();
forest.rollbackTransaction();
}
@Override
public final void flush() throws IOException, InterruptedException
{
checkDatabaseOpen();
forest.flush();
}
@Override
public void consolidateAll() throws IOException, InterruptedException
{
checkDatabaseOpen();
forest.consolidateAll();
}
@Override
public void close() throws IOException, InterruptedException
{
synchronized (STATIC_LOCK) {
if (databaseOpen) {
databaseOpen = false;
deadlockFixer.stop();
forest.close();
}
}
}
@Override
public final Configuration configuration()
{
return factory.configuration();
}
// DatabaseImpl interface
public final Factory factory()
{
return factory;
}
public final void reportCrash(Throwable crash)
{
this.crash.set(crash);
}
public static int pid()
{
return pid;
}
// For testing
public static void reset()
{
databaseOpen = false;
}
// For use by this subclasses
protected DatabaseImpl(Factory factory)
throws IOException, InterruptedException
{
this.factory = factory;
this.deadlockFixer = new DeadlockFixer(factory.lockManager());
Transaction.initialize(factory);
databaseOpen = true;
}
// For use by this package
TransactionManager transactionManager()
{
return forest;
}
void checkDatabaseOpen() throws IOException, InterruptedException
{
if (crash.get() != null) {
close();
LOG.log(Level.SEVERE, "Shutting down because of crash in thread managed by Erdo", crash.get());
}
if (!databaseOpen) {
throw new UsageError("No database is open");
}
}
// For use by this class
private static void setPID()
{
if (PID_SYSTEM_PROPERTY == null) {
throw new UsageError("The pid system variable must be set to the process id of the current process.");
}
try {
pid = Integer.parseInt(PID_SYSTEM_PROPERTY);
} catch (NumberFormatException e) {
throw new UsageError(
String.format("The pid system variable must be set to the process id of the current process. " +
"pid was set to %s",
PID_SYSTEM_PROPERTY));
}
}
private static void checkDatabaseClosed()
{
if (databaseOpen) {
throw new UsageError("A database is already open.");
}
}
private static Factory factory(Class<? extends Factory> factoryClass, Configuration configuration)
{
try {
Constructor<? extends Factory> factoryConstructor = factoryClass.getConstructor(Configuration.class);
return factoryConstructor.newInstance(configuration);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new UsageError(e);
}
}
private static ConfigurationImpl mergedConfiguration(File dbDirectory, Configuration overrideConfiguration)
throws IOException
{
ConfigurationImpl mergedConfiguration = new ConfigurationImpl();
DBStructure dbStructure = new DBStructure(dbDirectory);
try {
mergedConfiguration.read(dbStructure.dbPropertiesFile());
} catch (IOException e) {
throw new UsageError(e);
}
if (overrideConfiguration != null) {
mergedConfiguration.override(overrideConfiguration);
}
return mergedConfiguration;
}
// Class state
protected static final Logger LOG = Logger.getLogger(DatabaseImpl.class.getName());
private static final Object STATIC_LOCK = new Object();
private static final String PID_SYSTEM_PROPERTY = System.getProperty("pid", null);
private static int pid;
private static volatile boolean databaseOpen = false;
// Object state
protected final Factory factory;
protected final Map<String, OrderedMapImpl> maps = new HashMap<>();
protected Forest forest;
private final DeadlockFixer deadlockFixer;
private final AtomicReference<Throwable> crash = new AtomicReference<>(null);
}