com.kolibrifx.plovercrest.server.PlovercrestEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plovercrest-server Show documentation
Show all versions of plovercrest-server Show documentation
Plovercrest server library.
The newest version!
/*
* Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
*/
package com.kolibrifx.plovercrest.server;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import com.kolibrifx.common.Disposable;
import com.kolibrifx.plovercrest.client.PlovercrestException;
import com.kolibrifx.plovercrest.client.TableLockedException;
import com.kolibrifx.plovercrest.server.internal.EventDispatcher;
import com.kolibrifx.plovercrest.server.internal.EventType;
import com.kolibrifx.plovercrest.server.internal.TableInfoSerializer;
import com.kolibrifx.plovercrest.server.internal.TableListener;
import com.kolibrifx.plovercrest.server.internal.TableListenerCollection;
import com.kolibrifx.plovercrest.server.internal.TableListenerEntry;
import com.kolibrifx.plovercrest.server.internal.ThreadedEventDispatcher;
import com.kolibrifx.plovercrest.server.internal.engine.DataDirLock;
import com.kolibrifx.plovercrest.server.internal.engine.LeastRecentlyUsedCache;
public class PlovercrestEngine {
public static final int DEFAULT_TABLE_CACHE_SIZE = 1024;
private static final Logger log = Logger.getLogger(PlovercrestEngine.class);
private final Map tables = new HashMap();
private final String dataPath;
private final EventDispatcher dispatcher;
private final TableListenerCollection globalListeners = new TableListenerCollection();
private final LeastRecentlyUsedCache cache;
private final DataDirLock dataDirLock;
/**
* Creates a PlovercrestEngine with a threaded event dispatcher.
*/
public PlovercrestEngine(final String dataPath) {
this(dataPath, new ThreadedEventDispatcher(), new LeastRecentlyUsedCache(DEFAULT_TABLE_CACHE_SIZE));
}
/**
* Creates a PlovercrestEngine with the given event dispatcher.
*/
public PlovercrestEngine(final String dataPath,
final EventDispatcher dispatcher,
final LeastRecentlyUsedCache cache) {
this.dataPath = dataPath;
this.dispatcher = dispatcher;
this.cache = cache;
this.dataDirLock = new DataDirLock(dataPath);
refresh();
}
public Table create(final TableInfo info) {
synchronized (tables) {
final Table tbl = Table.create(dataPath, info, dispatcher, cache);
addNewTableToMap(tbl);
return tbl;
}
}
private void addNewTableToMap(final Table table) {
// tables lock must be held here
final String name = table.getName();
if (tables.containsKey(name)) {
final TableListenerEntry entry = tables.get(name);
entry.setTable(table);
table.dispatchEvent(EventType.TABLE_CREATED);
} else {
tables.put(name, new TableListenerEntry(table));
// here we have no listeners, so no need to trigger an event
}
globalListeners.dispatch(dispatcher, name, EventType.TABLE_CREATED);
}
private void removeFromMap(final String name, final TableListenerEntry entry) {
// tables lock must be held here
if (entry.hasListeners()) {
entry.setTable(null);
} else {
tables.remove(name);
}
globalListeners.dispatch(dispatcher, name, EventType.TABLE_DELETED);
}
public Set getTableNames() {
final HashSet r = new HashSet<>();
synchronized (tables) {
for (final TableListenerEntry entry : tables.values()) {
final Table table = entry.getTable();
if (table != null) {
r.add(table.getName());
}
}
}
return Collections.unmodifiableSet(r);
}
public boolean delete(final String name) {
synchronized (tables) {
if (!tables.containsKey(name)) {
return false;
}
final TableListenerEntry entry = tables.get(name);
if (entry.getTable() == null) {
return false;
}
if (entry.getTable().delete()) {
removeFromMap(name, entry);
return true;
}
}
return false;
}
void refresh() {
final File dir = new File(dataPath);
final File[] fileList = dir.listFiles();
if (fileList == null) {
throw new PlovercrestException("Failed to list contents of " + dir + ", non-existing directory?");
}
synchronized (tables) {
for (final File file : fileList) {
if (file.getName().endsWith(".info")) {
final String f = TableInfoSerializer.tableNameFromInfoFile(file);
if (!tables.containsKey(f)) {
try {
final Table t = Table.open(dataPath, file, dispatcher, cache);
tables.put(f, new TableListenerEntry(t));
} catch (final TableLockedException ex) {
log.error("Table locked: " + f);
// consider this a fatal error, rethrow
throw ex;
} catch (final PlovercrestException ex) {
log.error("Error opening table " + f, ex);
}
}
}
}
}
}
public Table open(final String name) {
synchronized (tables) {
final TableListenerEntry entry = tables.get(name);
if (entry == null) {
return null;
} else {
return entry.getTable();
}
}
}
public Table rename(final String oldName, final String newName) {
synchronized (tables) {
if (tables.containsKey(newName)) {
throw new PlovercrestException("Cannot rename table to " + newName + " since it already exists.");
}
final TableListenerEntry entry = tables.get(oldName);
if (entry == null || entry.getTable() == null) {
return null;
}
final Table oldTable = entry.getTable();
oldTable.close();
removeFromMap(oldName, entry);
if (!oldTable.rename(newName)) {
return null;
}
final File infoFile = Table.constructFileName(dataPath, newName, ".info");
if (!infoFile.exists()) {
return null;
}
final Table newTable = Table.open(dataPath, infoFile, dispatcher, cache);
addNewTableToMap(newTable);
return newTable;
}
}
/**
* Close all tables, and process any queued events.
*/
public void close() {
synchronized (tables) {
for (final TableListenerEntry t : tables.values()) {
if (t.getTable() != null) {
t.getTable().close();
}
}
tables.clear();
}
dispatcher.shutDown();
dataDirLock.close();
}
/**
* Adds a table listener. To remove it later, either call
* {@link #removeTableListener(String, TableListener)} or close the returned {@link Disposable}.
*/
public Disposable addTableListener(final String tableName, final TableListener listener) {
synchronized (tables) {
if (tables.containsKey(tableName)) {
tables.get(tableName).addListener(listener);
} else {
tables.put(tableName, new TableListenerEntry(listener));
}
}
return new Disposable() {
@Override
public void close() {
removeTableListener(tableName, listener);
}
};
}
public boolean removeTableListener(final String tableName, final TableListener listener) {
synchronized (tables) {
if (tables.containsKey(tableName)) {
return tables.get(tableName).removeListener(listener);
} else {
return false;
}
}
}
/**
* Adds a listener for global table events, which is a subset of the events for each table.
*/
public void addGlobalTableListener(final TableListener listener) {
globalListeners.add(listener);
}
public boolean removeGlobalListener(final TableListener listener) {
return globalListeners.remove(listener);
}
public EventDispatcher getDispatcher() {
return dispatcher;
}
public String getDataPath() {
return dataPath;
}
}