/*
* 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.OrderedMap;
import com.github.geophile.erdo.RecordFactory;
import com.github.geophile.erdo.UsageError;
import com.github.geophile.erdo.forest.Forest;
import com.github.geophile.erdo.forest.ForestRecovery;
import com.github.geophile.erdo.map.Factory;
import com.github.geophile.erdo.map.diskmap.DBStructure;
import com.github.geophile.erdo.util.FileUtil;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
public class DatabaseOnDisk extends DatabaseImpl
{
// Object interface
@Override
public String toString()
{
return String.format("Database(%s)", dbStructure.dbDirectory());
}
// Database interface
public static synchronized DatabaseOnDisk createDatabase(File dbDirectory, Factory factory)
throws IOException, InterruptedException
{
if (dbDirectory == null) {
throw new IllegalArgumentException("dbDirectory must not be null.");
}
DBStructure dbStructure = new DBStructure(dbDirectory);
FileUtil.ensureDirectoryExists(dbStructure.dbDirectory());
FileUtil.ensureDirectoryExists(dbStructure.forestDirectory());
FileUtil.ensureDirectoryExists(dbStructure.segmentsDirectory());
FileUtil.ensureDirectoryExists(dbStructure.summariesDirectory());
FileUtil.ensureDirectoryExists(dbStructure.mapsDirectory());
FileUtil.checkFileDoesNotExist(dbStructure.dbPropertiesFile());
ConfigurationImpl configuration = (ConfigurationImpl) factory.configuration();
configuration.map.write(dbStructure.dbPropertiesFile());
return new DatabaseOnDisk(factory, dbStructure, true);
}
public static synchronized DatabaseOnDisk openDatabase(File dbDirectory, Factory factory)
throws IOException, InterruptedException
{
if (dbDirectory == null) {
throw new IllegalArgumentException("dbDirectory must not be null.");
}
try {
FileUtil.checkDirectoryExists(dbDirectory);
} catch (IOException e) {
throw new UsageError(e);
}
return new DatabaseOnDisk(factory, new DBStructure(dbDirectory), false);
}
// synchronized to prevent race condition when two threads create maps with the same name at the
// same time.
public synchronized OrderedMap createMap(String mapName, RecordFactory recordFactory)
throws UsageError, IOException, InterruptedException
{
OrderedMapImpl map = ((OrderedMapImpl) super.createMap(mapName, recordFactory));
// Create file representing map
File mapFile = new File(dbStructure.mapsDirectory(), Integer.toString(map.erdoId()));
FileUtil.createFile(mapFile);
try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(mapFile))) {
output.writeObject(mapName);
output.writeObject(recordFactory);
}
return map;
}
@Override
public void close() throws IOException, InterruptedException
{
super.close();
FileUtil.deleteFile(dbStructure().pidFile());
}
// DatabaseOnDisk interface
public DBStructure dbStructure()
{
return dbStructure;
}
// For use by this package
DatabaseOnDisk(Factory factory, DBStructure dbStructure, boolean create)
throws IOException, InterruptedException
{
super(factory);
this.dbStructure = dbStructure;
writePID();
if (create) {
forest = Forest.create(this);
} else {
Map<String, Integer> mapNameToErdoId = new HashMap<>();
File[] mapFiles = dbStructure.mapsDirectory().listFiles();
assert mapFiles != null : dbStructure.mapsDirectory();
for (File mapFile : mapFiles) {
int erdoId = Integer.parseInt(mapFile.getName());
try (ObjectInputStream input = new ObjectInputStream(new FileInputStream(mapFile))) {
String mapName = (String) input.readObject();
RecordFactory recordFactory = (RecordFactory) input.readObject();
mapNameToErdoId.put(mapName, erdoId);
factory.registerRecordFactory(erdoId, recordFactory);
} catch (ClassNotFoundException e) {
throw new UsageError(e);
}
}
forest = null;
try {
ForestRecovery forestRecovery =
(ForestRecovery) factory().forestRecoveryClass().newInstance();
forest = forestRecovery.recoverForest(this);
} catch (InstantiationException | IllegalAccessException e) {
assert false : e;
}
assert forest != null;
for (Map.Entry<String, Integer> entry : mapNameToErdoId.entrySet()) {
maps.put(entry.getKey(),
new OrderedMapImpl(this, entry.getValue()));
}
}
factory.transactionManager(forest);
}
// For use by this class
private void writePID() throws IOException
{
File pidFile = dbStructure.pidFile();
if (pidFile.exists()) {
ByteBuffer buffer = FileUtil.readFile(pidFile);
String previousPID = new String(buffer.array(), 0, buffer.remaining());
throw new UsageError(String.format("Erdo process with pid %s already running?", previousPID));
} else {
String pid = System.getProperty("pid");
if (pid == null) {
throw new UsageError("Set pid system property to the pid of the process starting erdo.");
}
ByteBuffer buffer = ByteBuffer.allocate(MAX_PID_SIZE);
buffer.put(pid.getBytes());
buffer.flip();
FileUtil.writeFile(pidFile, buffer);
}
}
// Class state
private static final int MAX_PID_SIZE = 10;
// Object state
private final DBStructure dbStructure;
}