All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kolibrifx.plovercrest.server.PlovercrestEngine Maven / Gradle / Ivy

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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy