package com.plexobject.rbac.repository.bdb; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.apache.commons.validator.GenericValidator; import org.apache.log4j.Logger; import com.plexobject.rbac.Configuration; import com.plexobject.rbac.metric.Metric; import com.plexobject.rbac.metric.Timing; import com.plexobject.rbac.repository.PersistenceException; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.DeadlockException; import com.sleepycat.je.Durability; import com.sleepycat.je.Environment; import com.sleepycat.je.EnvironmentConfig; import com.sleepycat.je.EnvironmentLockedException; import com.sleepycat.je.Transaction; import com.sleepycat.persist.EntityStore; import com.sleepycat.persist.StoreConfig; import com.sleepycat.persist.evolve.IncompatibleClassException; public class DatabaseStore { private static final Logger LOGGER = Logger.getLogger(DatabaseStore.class); private static final String DATABASE_DIR = Configuration.getInstance() .getProperty("database.dir", "PlexRbacDB"); private Environment dbEnvironment; private StoreConfig storeConfig; private Map<String, EntityStore> stores = new HashMap<String, EntityStore>(); private Map<String, Database> databases = new HashMap<String, Database>(); private static ThreadLocal<Transaction> CURRENT_TXN = new ThreadLocal<Transaction>(); public DatabaseStore() { this(DATABASE_DIR); } public DatabaseStore(final String databaseDir) { if (GenericValidator.isBlankOrNull(databaseDir)) { throw new IllegalArgumentException("databaseDir is not specified"); } Durability defaultDurability = new Durability( Durability.SyncPolicy.SYNC, null, // unused by non-HA // applications. null); // unused by non-HA applications. // Open the DB environment. Create if they do not already exist. EnvironmentConfig envConfig = new EnvironmentConfig(); envConfig.setAllowCreate(true); envConfig.setTransactional(true); envConfig.setSharedCache(true); envConfig.setDurability(defaultDurability); // envConfig.setReadOnly(true); // envConfig.setTxnTimeout(1000000); final File dir = new File(databaseDir); if (!dir.exists()) { dir.mkdirs(); } // try { dbEnvironment = new Environment(dir, envConfig); } catch (EnvironmentLockedException e) { throw new PersistenceException(e); } catch (DatabaseException e) { throw new PersistenceException(e); } storeConfig = new StoreConfig(); storeConfig.setAllowCreate(true); storeConfig.setDeferredWrite(true); } // synchronized EntityStore getStore(final String domain) { if (GenericValidator.isBlankOrNull(domain)) { throw new IllegalArgumentException("domain is not specified"); } EntityStore store = stores.get(domain); if (store == null) { try { store = new EntityStore(dbEnvironment, domain, storeConfig); } catch (IncompatibleClassException e) { throw new PersistenceException(e); } catch (DatabaseException e) { throw new PersistenceException(e); } stores.put(domain, store); } return store; } Collection<String> getAllDatabases() throws PersistenceException { final Timing timer = Metric.newTiming(getClass().getName() + ".getAllDatabases"); try { return dbEnvironment.getDatabaseNames(); } catch (EnvironmentLockedException e) { throw new PersistenceException(e); } catch (DatabaseException e) { throw new PersistenceException(e); } finally { timer.stop(); } } void removeDatabase(final String domain) { final Timing timer = Metric.newTiming(getClass().getName() + ".removeDatabase"); close(domain); try { Database db = databases.get(domain); if (db != null) { db.close(); } dbEnvironment.removeDatabase(null, domain); } catch (DeadlockException e) { LOGGER.error("Failed to remove database " + domain + " - " + e); } catch (EnvironmentLockedException e) { throw new PersistenceException("Failed to remove database " + domain, e); } catch (DatabaseException e) { throw new PersistenceException("Failed to remove database " + domain, e); } finally { timer.stop(); } } void createDatabase(final String domain) { final Timing timer = Metric.newTiming(getClass().getName() + ".createDatabase"); try { DatabaseConfig dbconfig = new DatabaseConfig(); dbconfig.setAllowCreate(true); dbconfig.setSortedDuplicates(false); dbconfig.setExclusiveCreate(false); dbconfig.setReadOnly(false); dbconfig.setTransactional(true); Database db = dbEnvironment.openDatabase(null, domain, dbconfig); databases.put(domain, db); getStore(domain); } catch (DatabaseException e) { throw new PersistenceException("Failed to create database " + domain, e); } finally { timer.stop(); } } synchronized void close(final String domain) { EntityStore store = stores.remove(domain); if (store != null) { try { store.close(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Closed database " + domain); } } catch (IllegalStateException e) { // already closed LOGGER.warn("Aready closed database " + domain + ": " + e); } catch (DatabaseException e) { throw new PersistenceException(e); } } } void close() throws DatabaseException { if (dbEnvironment == null) { LOGGER.warn("already closed"); return; } for (String domain : new ArrayList<String>(stores.keySet())) { close(domain); } dbEnvironment.sync(); dbEnvironment.close(); dbEnvironment = null; } void beginTransaction() { if (CURRENT_TXN.get() != null) { throw new IllegalStateException("Already in transaction"); } try { CURRENT_TXN.set(dbEnvironment.beginTransaction(null, null)); } catch (DatabaseException e) { throw new PersistenceException(e); } } void commitTransaction() { Transaction txn = CURRENT_TXN.get(); try { if (txn != null) { txn.commit(); } } catch (DatabaseException e) { throw new PersistenceException(e); } finally { CURRENT_TXN.set(null); } } void abortTransaction() { Transaction txn = CURRENT_TXN.get(); try { if (txn != null) { txn.abort(); } } catch (DatabaseException e) { throw new PersistenceException(e); } finally { CURRENT_TXN.set(null); } } }