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

org.jsimpledb.kv.bdb.BerkeleyKVDatabase Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package org.jsimpledb.kv.bdb;

import com.google.common.base.Preconditions;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.jsimpledb.kv.KVDatabase;
import org.jsimpledb.kv.KVDatabaseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Oracle Berkeley DB Java Edition {@link KVDatabase} implementation.
 *
 * 

* A {@linkplain #setDirectory database directory} is the only required configuration property, but the * {@link Environment}, {@link Database}, and {@link Transaction}s may all be configured. * Instances may be stopped and (re)started multiple times. *

* * @see Oracle Berkeley DB Java Edition */ public class BerkeleyKVDatabase implements KVDatabase { // Locking order: (1) BerkeleyKVTransaction, (2) BerkeleyKVDatabase /** * Default Berkeley DB database name ({@value #DEFAULT_DATABASE_NAME}). * * @see #setDatabaseName setDatabaseName() */ public static final String DEFAULT_DATABASE_NAME = "JSimpleDB"; private static final ThreadLocal NEXT_TX_CONFIG = new ThreadLocal<>(); private final Logger log = LoggerFactory.getLogger(this.getClass()); private final DatabaseConfig databaseConfig = new DatabaseConfig() .setAllowCreate(true) .setTransactional(true); private final HashSet openTransactions = new HashSet<>(); private EnvironmentConfig environmentConfig = new EnvironmentConfig() .setAllowCreate(true) .setTransactional(true) .setTxnSerializableIsolation(true); private TransactionConfig defaultTransactionConfig = TransactionConfig.DEFAULT; private File directory; private String databaseName = DEFAULT_DATABASE_NAME; private Environment environment; private Database database; /** * Constructor. */ public BerkeleyKVDatabase() { } /** * Get the filesystem directory containing the database. * * @return database directory */ public synchronized File getDirectory() { return this.directory; } /** * Configure the filesystem directory containing the database. Required property. * * @param directory database directory */ public synchronized void setDirectory(File directory) { this.directory = directory; } /** * Get the configured {@link Database} name. * * @return database name */ public synchronized String getDatabaseName() { return this.databaseName; } /** * Configure the {@link Database} name. Defaults to {@link #DEFAULT_DATABASE_NAME}. * * @param databaseName database name */ public synchronized void setDatabaseName(String databaseName) { this.databaseName = databaseName; } /** * Get the {@link EnvironmentConfig} to be used by this instance. * *

* This method returns a copy; use {@link #setEnvironmentConfig setEnvironmentConfig()} to change. *

* * @return environment config */ public synchronized EnvironmentConfig getEnvironmentConfig() { return this.environmentConfig.clone(); } /** * Set a custom {@link EnvironmentConfig} to be used by this instance at startup. * *

* The default {@link EnvironmentConfig} is configured for * {@linkplain EnvironmentConfig#getTxnSerializableIsolation serializable} * {@linkplain EnvironmentConfig#getTransactional transactional} operation and with * {@linkplain EnvironmentConfig#getAllowCreate allowCreate} set to true. *

* *

* The given {@code config} must at least be configured for transactional operation. *

* * @param config environment config * @throws IllegalArgumentException if {@code config} is not configured to be * {@linkplain EnvironmentConfig#getTransactional transactional} * @throws IllegalArgumentException if {@code config} is null */ public synchronized void setEnvironmentConfig(EnvironmentConfig config) { Preconditions.checkArgument(config != null, "null config"); Preconditions.checkArgument(config.getTransactional(), "environment config must be transactional"); this.environmentConfig = config; } /** * Get the default {@link TransactionConfig} to be used by this instance. * *

* This method returns a copy; use {@link #setTransactionConfig setTransactionConfig()} to change. *

* * @return transaction config */ public synchronized TransactionConfig getTransactionConfig() { return this.defaultTransactionConfig.clone(); } /** * Configure a custom default {@link TransactionConfig} to be used by this instance for transactions. * *

* Note: this configures the default; this default config can be overridden for the next transaction in the * current thread only via {@link #setNextTransactionConfig setNextTransactionConfig()}. *

* *

* The default setting for this property is {@link TransactionConfig#DEFAULT}. *

* * @param config transaction config * @throws IllegalArgumentException if {@code config} is null * @see #setNextTransactionConfig setNextTransactionConfig() */ public synchronized void setTransactionConfig(TransactionConfig config) { Preconditions.checkArgument(config != null, "null config"); this.defaultTransactionConfig = config; } /** * Configure a custom {@link TransactionConfig} to be used for the next transaction in the current thread. * The next, and only the next, invocation of {@link #createTransaction} in the current thread will * use the given configuration. After that, subsequent transactions will revert back to the * {@linkplain #setTransactionConfig default} (even if the invocation of {@link #createTransaction} failed). * * @param config transaction config * @throws IllegalArgumentException if {@code config} is null * @see #setTransactionConfig setTransactionConfig() */ public void setNextTransactionConfig(TransactionConfig config) { Preconditions.checkArgument(config != null, "null config"); NEXT_TX_CONFIG.set(config); } /** * Get the {@link DatabaseConfig} to be used by this instance. * *

* This method returns a copy; use {@link #setDatabaseConfig setDatabaseConfig()} to change. *

* * @return database config */ public synchronized DatabaseConfig getDatabaseConfig() { return this.databaseConfig.clone(); } /** * Apply selected database configuration parameters from the given instance to the * {@link DatabaseConfig} this instance will use when opening the {@link Database} at startup. * *

* The default {@link DatabaseConfig} is configured for transactional operation and with * {@linkplain EnvironmentConfig#getAllowCreate allowCreate} set to true. *

* * Only certain allowed configuration properties are copied. The copied properties are: *
    *
  • {@linkplain DatabaseConfig#getAllowCreate allowCreate}
  • *
  • {@linkplain DatabaseConfig#getCacheMode cacheMode}
  • *
  • {@linkplain DatabaseConfig#getDeferredWrite deferredWrite}
  • *
  • {@linkplain DatabaseConfig#getExclusiveCreate exclusiveCreate}
  • *
  • {@linkplain DatabaseConfig#getNodeMaxEntries nodeMaxEntries}
  • *
  • {@linkplain DatabaseConfig#getReadOnly readOnly}
  • *
  • {@linkplain DatabaseConfig#getReplicated replicated}
  • *
  • {@linkplain DatabaseConfig#getTemporary temporary}
  • *
* * @param config database config */ public synchronized void setDatabaseConfig(DatabaseConfig config) { this.databaseConfig .setAllowCreate(config.getAllowCreate()) .setCacheMode(config.getCacheMode()) .setDeferredWrite(config.getDeferredWrite()) .setExclusiveCreate(config.getExclusiveCreate()) .setNodeMaxEntries(config.getNodeMaxEntries()) .setReadOnly(config.getReadOnly()) .setReplicated(config.getReplicated()) .setTemporary(config.getTemporary()); } /** * Get the underlying {@link Environment} associated with this instance. * * @return the associated {@link Environment} * @throws IllegalStateException if this instance is not {@linkplain #start started} */ public synchronized Environment getEnvironment() { Preconditions.checkState(this.environment != null, "not started"); assert this.database != null; return this.environment; } /** * Get the underlying {@link Database} associated with this instance. * * @return the associated {@link Database} * @throws IllegalStateException if this instance is not {@linkplain #start started} */ public synchronized Database getDatabase() { Preconditions.checkState(this.environment != null, "not started"); assert this.database != null; return this.database; } // KVDatabase @Override public BerkeleyKVTransaction createTransaction(Map options) { return this.createTransaction(); // no options supported yet } @Override public synchronized BerkeleyKVTransaction createTransaction() { // Check open Preconditions.checkState(this.environment != null, "not started"); assert this.database != null; // Get the config for this transaction TransactionConfig config = NEXT_TX_CONFIG.get(); if (config == null) config = this.defaultTransactionConfig; else NEXT_TX_CONFIG.remove(); // Create the transaction final Transaction bdbTx; try { bdbTx = this.environment.beginTransaction(null, config); } catch (DatabaseException e) { throw new KVDatabaseException(this, e); } final BerkeleyKVTransaction tx = new BerkeleyKVTransaction(this, bdbTx); // Record transaction for possible cleanup on shutdown this.openTransactions.add(tx); // Done return tx; } /** * Remove a transaction that is now closed. */ synchronized void removeTransaction(BerkeleyKVTransaction tx) { this.openTransactions.remove(tx); } @Override @PostConstruct public synchronized void start() { if (this.environment != null) { assert this.database != null; return; } assert this.openTransactions.isEmpty(); Preconditions.checkState(this.directory != null, "no directory configured"); Preconditions.checkState(this.databaseName != null, "no database name configured"); boolean success = false; try { this.environment = new Environment(this.directory, this.environmentConfig); this.database = this.environment.openDatabase(null, this.databaseName, this.databaseConfig); success = true; } finally { if (!success) { try { for (AutoCloseable item : new AutoCloseable[] { this.database, this.environment }) { if (item != null) { try { item.close(); } catch (Throwable e) { this.log.error("caught exception cleaning up after failed startup (ignoring)", e); } } } } finally { this.database = null; this.environment = null; } } } } @Override @PreDestroy public void stop() { // Mark this instance as shutdown so no new transactions are created final Environment oldEnvironment; final Database oldDatabase; final ArrayList oldTransactions; synchronized (this) { oldEnvironment = this.environment; oldDatabase = this.database; oldTransactions = new ArrayList(this.openTransactions); this.environment = null; this.database = null; this.openTransactions.clear(); } // Were we already stopped? if (oldEnvironment == null) { assert this.database == null; return; } // Rollback any open transactions so cursors are cleaned up and transactions closed for (BerkeleyKVTransaction tx : oldTransactions) { try { tx.rollback(); } catch (Throwable e) { this.log.debug("caught exception closing open transaction during shutdown (ignoring)", e); } } // Shut down database try { oldDatabase.close(); } catch (Throwable e) { this.log.error("caught exception closing database during shutdown (ignoring)", e); } // Shut down environment try { oldEnvironment.close(); } catch (Throwable e) { this.log.error("caught exception closing environment during shutdown (ignoring)", e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy