/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2005
* Sleepycat Software. All rights reserved.
*
* $Id: Database.java,v 1.199 2005/09/21 18:48:11 linda Exp $
*/
package com.sleepycat.je;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.GetMode;
import com.sleepycat.je.dbi.PutMode;
import com.sleepycat.je.dbi.TruncateResult;
import com.sleepycat.je.dbi.CursorImpl.SearchMode;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.LockerFactory;
import com.sleepycat.je.utilint.Tracer;
public class Database {
/*
* DbState embodies the Database handle state.
*/
static class DbState {
private String stateName;
DbState(String stateName) {
this.stateName = stateName;
}
public String toString() {
return "DbState." + stateName;
}
}
static DbState OPEN = new DbState("OPEN");
static DbState CLOSED = new DbState("CLOSED");
static DbState INVALID = new DbState("INVALID");
/* The current state of the handle. */
private DbState state;
/* Handles onto the owning environment and the databaseImpl object. */
Environment envHandle; // used by subclasses
private DatabaseImpl databaseImpl;
DatabaseConfig configuration; // properties used at execution
/* True if this handle permits write operations; */
private boolean isWritable;
/* Transaction that owns the db lock held while the Database is open. */
Locker handleLocker;
/* Set of cursors open against this db handle. */
private Set cursors = new HashSet();
/*
* DatabaseTrigger list. The list is null if empty, and is checked for
* null to avoiding read locking overhead when no triggers are present.
* Access to this list is protected by the shared trigger latch in
* EnvironmentImpl.
*/
private List triggerList;
/**
* Creates a database but does not open or fully initialize it.
* Is protected for use in compat package.
*/
protected Database(Environment env) {
this.envHandle = env;
handleLocker = null;
}
/**
* Create a database, called by Environment.
*/
void initNew(Environment env,
Locker locker,
String databaseName,
DatabaseConfig dbConfig)
throws DatabaseException {
if (dbConfig.getReadOnly()) {
throw new DatabaseException
("DatabaseConfig.setReadOnly() must be set to false " +
"when creating a Database");
}
init(env, dbConfig);
/* Make the databaseImpl. */
EnvironmentImpl environmentImpl =
DbInternal.envGetEnvironmentImpl(envHandle);
databaseImpl = environmentImpl.createDb(locker,
databaseName,
dbConfig,
this);
databaseImpl.addReferringHandle(this);
}
/**
* Open a database, called by Environment.
*/
void initExisting(Environment env,
Locker locker,
DatabaseImpl databaseImpl,
DatabaseConfig dbConfig)
throws DatabaseException {
/*
* Make sure the configuration used for the open is compatible with the
* existing databaseImpl.
*/
validateConfigAgainstExistingDb(dbConfig, databaseImpl);
init(env, dbConfig);
this.databaseImpl = databaseImpl;
databaseImpl.addReferringHandle(this);
/*
* Copy the duplicates and transactional properties of the underlying
* database, in case the useExistingConfig property is set.
*/
configuration.setSortedDuplicates(databaseImpl.getSortedDuplicates());
configuration.setTransactional(databaseImpl.isTransactional());
}
private void init(Environment env,
DatabaseConfig config)
throws DatabaseException {
handleLocker = null;
envHandle = env;
configuration = config.cloneConfig();
isWritable = !configuration.getReadOnly();
state = OPEN;
}
/**
* See if this new handle's configuration is compatible with the
* pre-existing database.
*/
private void validateConfigAgainstExistingDb(DatabaseConfig config,
DatabaseImpl databaseImpl)
throws DatabaseException {
/*
* The allowDuplicates property is persistent and immutable. It does
* not need to be specified if the useExistingConfig property is set.
*/
if (!config.getUseExistingConfig()) {
if (databaseImpl.getSortedDuplicates() !=
config.getSortedDuplicates()) {
throw new DatabaseException
("You can't open a Database with a duplicatesAllowed " +
"configuration of " +
config.getSortedDuplicates() +
" if the underlying database was created with a " +
"duplicatesAllowedSetting of " +
databaseImpl.getSortedDuplicates() + ".");
}
}
/*
* The transactional property is kept constant while any handles are
* open, and set when the first handle is opened. It does not need to
* be specified if the useExistingConfig property is set.
*/
if (databaseImpl.hasOpenHandles()) {
if (!config.getUseExistingConfig()) {
if (config.getTransactional() !=
databaseImpl.isTransactional()) {
throw new DatabaseException
("You can't open a Database with a transactional " +
"configuration of " + config.getTransactional() +
" if the underlying database was created with a " +
"transactional configuration of " +
databaseImpl.isTransactional() + ".");
}
}
} else {
databaseImpl.setTransactional(config.getTransactional());
}
/*
* Only re-set the comparators if the override is allowed.
*/
if (config.getOverrideBtreeComparator()) {
databaseImpl.setBtreeComparator(config.getBtreeComparator());
}
if (config.getOverrideDuplicateComparator()) {
databaseImpl.setDuplicateComparator
(config.getDuplicateComparator());
}
}
public synchronized void close()
throws DatabaseException {
StringBuffer errors = null;
checkEnv();
checkProhibitedDbState(CLOSED, "Can't close Database:");
trace(Level.FINEST, "Database.close: ", null, null);
/* Disassociate triggers before closing. */
removeAllTriggers();
envHandle.removeReferringHandle(this);
if (cursors.size() > 0) {
errors = new StringBuffer
("There are open cursors against the database.\n");
errors.append("They will be closed.\n");
/*
* Copy the cursors set before iterating since the dbc.close()
* mutates the set.
*/
Iterator iter = new HashSet(cursors).iterator();
while (iter.hasNext()) {
Cursor dbc = (Cursor) iter.next();
try {
dbc.close();
} catch (DatabaseException DBE) {
errors.append("Exception while closing cursors:\n");
errors.append(DBE.toString());
}
}
}
if (databaseImpl != null) {
databaseImpl.removeReferringHandle(this);
databaseImpl = null;
/*
* Tell our protecting txn that we're closing. If this type
* of transaction doesn't live beyond the life of the handle,
* it will release the db handle lock.
*/
handleLocker.setHandleLockOwner(true, this, true);
handleLocker.operationEnd(true);
state = CLOSED;
}
if (errors != null) {
throw new DatabaseException(errors.toString());
}
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public Sequence openSequence(Transaction txn,
DatabaseEntry key,
SequenceConfig config)
throws DatabaseException {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
checkRequiredDbState(OPEN, "Can't call Database.openSequence:");
checkWritable("openSequence");
trace(Level.FINEST, "Database.openSequence", txn, key, null, null);
return new Sequence(this, txn, key, config);
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public void removeSequence(Transaction txn, DatabaseEntry key)
throws DatabaseException {
delete(txn, key);
}
public synchronized Cursor openCursor(Transaction txn,
CursorConfig cursorConfig)
throws DatabaseException {
checkEnv();
checkRequiredDbState(OPEN, "Can't open a cursor");
CursorConfig useConfig =
(cursorConfig == null) ? CursorConfig.DEFAULT : cursorConfig;
if (useConfig.getReadUncommitted() && useConfig.getReadCommitted()) {
throw new IllegalArgumentException
("Only one may be specified: ReadCommitted or ReadUncommitted");
}
trace(Level.FINEST, "Database.openCursor", txn, cursorConfig);
Cursor ret = newDbcInstance(txn, useConfig);
return ret;
}
/**
* Is overridden by SecondaryDatabase.
*/
Cursor newDbcInstance(Transaction txn,
CursorConfig cursorConfig)
throws DatabaseException {
return new Cursor(this, txn, cursorConfig);
}
public OperationStatus delete(Transaction txn, DatabaseEntry key)
throws DatabaseException {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
checkRequiredDbState(OPEN, "Can't call Database.delete:");
checkWritable("delete");
trace(Level.FINEST, "Database.delete", txn, key, null, null);
OperationStatus commitStatus = OperationStatus.NOTFOUND;
Locker locker = null;
try {
locker = LockerFactory.getWritableLocker
(envHandle, txn, isTransactional());
commitStatus = deleteInternal(locker, key);
return commitStatus;
} finally {
if (locker != null) {
locker.operationEnd(commitStatus);
}
}
}
/**
* Internal version of delete() that does no parameter checking. Notify
* triggers. Deletes all duplicates.
*/
OperationStatus deleteInternal(Locker locker, DatabaseEntry key)
throws DatabaseException {
Cursor cursor = null;
try {
cursor = new Cursor(this, locker, null);
OperationStatus commitStatus = OperationStatus.NOTFOUND;
/* Position a cursor at the specified data record. */
DatabaseEntry oldData = new DatabaseEntry();
OperationStatus searchStatus =
cursor.search(key, oldData, LockMode.RMW, SearchMode.SET);
/* Delete all records with that key. */
if (searchStatus == OperationStatus.SUCCESS) {
do {
/*
* Notify triggers before the actual deletion so that a
* primary record never exists while secondary keys refer
* to it. This is relied on by secondary read-uncommitted.
*/
if (hasTriggers()) {
notifyTriggers(locker, key, oldData, null);
}
/* The actual deletion. */
commitStatus = cursor.deleteNoNotify();
if (commitStatus != OperationStatus.SUCCESS) {
return commitStatus;
}
/* Get another duplicate. */
if (databaseImpl.getSortedDuplicates()) {
searchStatus =
cursor.retrieveNext(key, oldData,
LockMode.RMW,
GetMode.NEXT_DUP);
} else {
searchStatus = OperationStatus.NOTFOUND;
}
} while (searchStatus == OperationStatus.SUCCESS);
commitStatus = OperationStatus.SUCCESS;
}
return commitStatus;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public OperationStatus get(Transaction txn,
DatabaseEntry key,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", false);
checkRequiredDbState(OPEN, "Can't call Database.get:");
trace(Level.FINEST, "Database.get", txn, key, null, lockMode);
CursorConfig cursorConfig = CursorConfig.DEFAULT;
if (lockMode == LockMode.READ_COMMITTED) {
cursorConfig = CursorConfig.READ_COMMITTED;
lockMode = null;
}
Cursor cursor = null;
try {
cursor = new Cursor(this, txn, cursorConfig);
return cursor.search(key, data, lockMode, SearchMode.SET);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public OperationStatus getSearchBoth(Transaction txn,
DatabaseEntry key,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
checkRequiredDbState(OPEN, "Can't call Database.getSearchBoth:");
trace(Level.FINEST, "Database.getSearchBoth", txn, key, data,
lockMode);
CursorConfig cursorConfig = CursorConfig.DEFAULT;
if (lockMode == LockMode.READ_COMMITTED) {
cursorConfig = CursorConfig.READ_COMMITTED;
lockMode = null;
}
Cursor cursor = null;
try {
cursor = new Cursor(this, txn, cursorConfig);
return cursor.search(key, data, lockMode, SearchMode.BOTH);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public OperationStatus put(Transaction txn,
DatabaseEntry key,
DatabaseEntry data)
throws DatabaseException {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
DatabaseUtil.checkForPartialKey(key);
checkRequiredDbState(OPEN, "Can't call Database.put");
checkWritable("put");
trace(Level.FINEST, "Database.put", txn, key, data, null);
return putInternal(txn, key, data, PutMode.OVERWRITE);
}
public OperationStatus putNoOverwrite(Transaction txn,
DatabaseEntry key,
DatabaseEntry data)
throws DatabaseException {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
DatabaseUtil.checkForPartialKey(key);
checkRequiredDbState(OPEN, "Can't call Database.putNoOverWrite");
checkWritable("putNoOverwrite");
trace(Level.FINEST, "Database.putNoOverwrite", txn, key, data, null);
return putInternal(txn, key, data, PutMode.NOOVERWRITE);
}
public OperationStatus putNoDupData(Transaction txn,
DatabaseEntry key,
DatabaseEntry data)
throws DatabaseException {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
DatabaseUtil.checkForPartialKey(key);
checkRequiredDbState(OPEN, "Can't call Database.putNoDupData");
checkWritable("putNoDupData");
trace(Level.FINEST, "Database.putNoDupData", txn, key, data, null);
return putInternal(txn, key, data, PutMode.NODUP);
}
/**
* Internal version of put() that does no parameter checking.
*/
OperationStatus putInternal(Transaction txn,
DatabaseEntry key,
DatabaseEntry data,
PutMode putMode)
throws DatabaseException {
Locker locker = null;
Cursor cursor = null;
OperationStatus commitStatus = OperationStatus.KEYEXIST;
try {
locker = LockerFactory.getWritableLocker
(envHandle, txn, isTransactional());
cursor = new Cursor(this, locker, null);
commitStatus = cursor.putInternal(key, data, putMode);
return commitStatus;
} finally {
if (cursor != null) {
cursor.close();
}
if (locker != null) {
locker.operationEnd(commitStatus);
}
}
}
/**
*/
public JoinCursor join(Cursor[] cursors, JoinConfig config)
throws DatabaseException {
checkEnv();
checkRequiredDbState(OPEN, "Can't call Database.join");
DatabaseUtil.checkForNullParam(cursors, "cursors");
if (cursors.length == 0) {
throw new IllegalArgumentException
("At least one cursor is required.");
}
/*
* Check that all cursors use the same locker, if any cursor is
* transactional. And if non-transactional, that all databases are in
* the same environment.
*/
Locker locker = cursors[0].getCursorImpl().getLocker();
if (!locker.isTransactional()) {
EnvironmentImpl env = envHandle.getEnvironmentImpl();
for (int i = 1; i < cursors.length; i += 1) {
Locker locker2 = cursors[i].getCursorImpl().getLocker();
if (locker2.isTransactional()) {
throw new IllegalArgumentException
("All cursors must use the same transaction.");
}
EnvironmentImpl env2 = cursors[i].getDatabaseImpl()
.getDbEnvironment();
if (env != env2) {
throw new IllegalArgumentException
("All cursors must use the same environment.");
}
}
locker = null; /* Don't reuse a non-transactional locker. */
} else {
for (int i = 1; i < cursors.length; i += 1) {
Locker locker2 = cursors[i].getCursorImpl().getLocker();
if (locker.getTxnLocker() != locker2.getTxnLocker()) {
throw new IllegalArgumentException
("All cursors must use the same transaction.");
}
}
}
/* Create the join cursor. */
return new JoinCursor(locker, this, cursors, config);
}
/**
* @deprecated It has not been possible to implement this method with
* correct transactional semantics without incurring a performance penalty
* on all Database operations. Truncate functionality has been moved to
* Environment.truncateDatabase(), which requires that all Database handles
* on the database are closed before the truncate operation can execute.
*/
public int truncate(Transaction txn, boolean countRecords)
throws DatabaseException {
checkEnv();
checkRequiredDbState(OPEN, "Can't call Database.truncate");
checkWritable("truncate");
Tracer.trace(Level.FINEST,
envHandle.getEnvironmentImpl(),
"Database.truncate: txnId=" +
((txn == null) ? "null" : Long.toString(txn.getId())));
Locker locker = null;
boolean triggerLock = false;
boolean operationOk = false;
try {
locker = LockerFactory.getWritableLocker
(envHandle, txn, isTransactional(), true /*retainLocks*/,
null);
/*
* Pass true to always get a read lock on the triggers, so we are
* sure that no secondaries are added during truncation.
*/
acquireTriggerListReadLock();
triggerLock = true;
/* Truncate primary. */
int count = truncateInternal(locker, countRecords);
/* Truncate secondaries. */
for (int i = 0; i < triggerList.size(); i += 1) {
Object obj = triggerList.get(i);
if (obj instanceof SecondaryTrigger) {
SecondaryDatabase secDb = ((SecondaryTrigger) obj).getDb();
secDb.truncateInternal(locker, false);
}
}
operationOk = true;
return count;
} finally {
if (locker != null) {
locker.operationEnd(operationOk);
}
if (triggerLock) {
releaseTriggerListReadLock();
}
}
}
/**
* Internal unchecked truncate that optionally counts records.
* @deprecated
*/
int truncateInternal(Locker locker, boolean countRecords)
throws DatabaseException {
if (databaseImpl == null) {
throw new DatabaseException
("couldn't find database - truncate");
}
databaseImpl.checkIsDeleted("truncate");
/*
* Truncate must obtain a write lock. In order to do so, it assumes
* ownership for the handle lock and transfers it from this Database
* object to the txn.
*/
if (handleLocker.isHandleLockTransferrable()) {
handleLocker.transferHandleLock(this, locker, false);
}
boolean operationOk = false;
try {
/*
* truncate clones the existing database and returns a new one to
* replace it with. The old databaseImpl object is marked
* 'deleted'.
*/
TruncateResult result =
envHandle.getEnvironmentImpl().truncate(locker, databaseImpl);
databaseImpl = result.getDatabase();
operationOk = true;
return countRecords ? result.getRecordCount() : -1;
} finally {
/*
* The txn will know if it's living past the end of this operation,
* and if it needs to transfer the handle lock. operationEnd()
* will be called one level up by the public truncate() method.
*/
locker.setHandleLockOwner(operationOk, this, false);
}
}
/*
* @deprecated As of JE 2.0.55, replaced by
* {@link Database#preload(long, long)}.
*/
public void preload(long maxBytes)
throws DatabaseException {
checkEnv();
checkRequiredDbState(OPEN, "Can't call Database.preload");
databaseImpl.checkIsDeleted("preload");
databaseImpl.preload(maxBytes, 0);
}
public void preload(long maxBytes, long maxMillisecs)
throws DatabaseException {
checkEnv();
checkRequiredDbState(OPEN, "Can't call Database.preload");
databaseImpl.checkIsDeleted("preload");
databaseImpl.preload(maxBytes, maxMillisecs);
}
public DatabaseStats getStats(StatsConfig config)
throws DatabaseException {
checkEnv();
checkRequiredDbState(OPEN, "Can't call Database.stat");
StatsConfig useConfig =
(config == null) ? StatsConfig.DEFAULT : config;
if (databaseImpl != null) {
databaseImpl.checkIsDeleted("stat");
return databaseImpl.stat(useConfig);
}
return null;
}
public DatabaseStats verify(VerifyConfig config)
throws DatabaseException {
checkEnv();
checkRequiredDbState(OPEN, "Can't call Database.verify");
databaseImpl.checkIsDeleted("verify");
VerifyConfig useConfig =
(config == null) ? VerifyConfig.DEFAULT : config;
DatabaseStats stats = databaseImpl.getEmptyStats();
databaseImpl.verify(useConfig, stats);
return stats;
}
public String getDatabaseName()
throws DatabaseException {
checkEnv();
if (databaseImpl != null) {
return databaseImpl.getName();
} else {
return null;
}
}
/*
* Non-transactional database name, safe to access when creating error
* messages.
*/
String getDebugName() {
if (databaseImpl != null) {
return databaseImpl.getDebugName();
} else {
return null;
}
}
public DatabaseConfig getConfig()
throws DatabaseException {
DatabaseConfig showConfig = configuration.cloneConfig();
/*
* Set the comparators from the database impl, they might have
* changed from another handle.
*/
Comparator btComp =
(databaseImpl == null ?
null :
databaseImpl.getBtreeComparator());
Comparator dupComp =
(databaseImpl == null ?
null :
databaseImpl.getDuplicateComparator());
showConfig.setBtreeComparator
(btComp == null ? null : btComp.getClass());
showConfig.setDuplicateComparator
(dupComp == null ? null: dupComp.getClass());
return showConfig;
}
/**
* Equivalent to getConfig().getTransactional() but cheaper.
*/
boolean isTransactional()
throws DatabaseException {
return databaseImpl.isTransactional();
}
public Environment getEnvironment()
throws DatabaseException {
return envHandle;
}
public List getSecondaryDatabases()
throws DatabaseException {
List list = new ArrayList();
if (hasTriggers()) {
acquireTriggerListReadLock();
try {
for (int i = 0; i < triggerList.size(); i += 1) {
Object obj = triggerList.get(i);
if (obj instanceof SecondaryTrigger) {
list.add(((SecondaryTrigger) obj).getDb());
}
}
} finally {
releaseTriggerListReadLock();
}
} else {
}
return list;
}
/*
* Helpers, not part of the public API
*/
/**
* @return true if the Database was opened read/write.
*/
boolean isWritable() {
return isWritable;
}
/**
* Return the databaseImpl object instance.
*/
DatabaseImpl getDatabaseImpl() {
return databaseImpl;
}
/**
* The handleLocker is the one that holds the db handle lock.
*/
void setHandleLocker(Locker locker) {
handleLocker = locker;
}
synchronized void removeCursor(Cursor dbc) {
cursors.remove(dbc);
}
synchronized void addCursor(Cursor dbc) {
cursors.add(dbc);
}
/**
* @throws DatabaseException if the Database state is not this value.
*/
void checkRequiredDbState(DbState required, String msg)
throws DatabaseException {
if (state != required) {
throw new DatabaseException
(msg + " Database state can't be " + state +
" must be " + required);
}
}
/**
* @throws DatabaseException if the Database state is this value.
*/
void checkProhibitedDbState(DbState prohibited, String msg)
throws DatabaseException {
if (state == prohibited) {
throw new DatabaseException
(msg + " Database state must not be " + prohibited);
}
}
/**
* @throws RunRecoveryException if the underlying environment is
* invalid
*/
void checkEnv()
throws RunRecoveryException {
EnvironmentImpl env = envHandle.getEnvironmentImpl();
if (env != null) {
env.checkIfInvalid();
}
}
/**
* Invalidate the handle, called by txn.abort by way of DbInternal.
*/
synchronized void invalidate() {
state = INVALID;
envHandle.removeReferringHandle(this);
if (databaseImpl != null) {
databaseImpl.removeReferringHandle(this);
}
}
/**
* Check that write operations aren't used on a readonly Database.
*/
private void checkWritable(String operation)
throws DatabaseException {
if (!isWritable) {
throw new DatabaseException
("Database is Read Only: " + operation);
}
}
/**
* Send trace messages to the java.util.logger. Don't rely on the logger
* alone to conditionalize whether we send this message, we don't even want
* to construct the message if the level is not enabled.
*/
void trace(Level level,
String methodName,
Transaction txn,
DatabaseEntry key,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
Logger logger = envHandle.getEnvironmentImpl().getLogger();
if (logger.isLoggable(level)) {
StringBuffer sb = new StringBuffer();
sb.append(methodName);
if (txn != null) {
sb.append(" txnId=").append(txn.getId());
}
sb.append(" key=").append(key.dumpData());
if (data != null) {
sb.append(" data=").append(data.dumpData());
}
if (lockMode != null) {
sb.append(" lockMode=").append(lockMode);
}
logger.log(level, sb.toString());
}
}
/**
* Send trace messages to the java.util.logger. Don't rely on the logger
* alone to conditionalize whether we send this message, we don't even want
* to construct the message if the level is not enabled.
*/
void trace(Level level,
String methodName,
Transaction txn,
CursorConfig config)
throws DatabaseException {
Logger logger = envHandle.getEnvironmentImpl().getLogger();
if (logger.isLoggable(level)) {
StringBuffer sb = new StringBuffer();
sb.append(methodName);
sb.append(" name=" + getDebugName());
if (txn != null) {
sb.append(" txnId=").append(txn.getId());
}
if (config != null) {
sb.append(" config=").append(config);
}
logger.log(level, sb.toString());
}
}
/*
* Manage triggers.
*/
/**
* Returns whether any triggers are currently associated with this primary.
* Note that an update of the trigger list may be in progress and this
* method does not wait for that update to be completed.
*/
boolean hasTriggers() {
return triggerList != null;
}
/**
* Gets a read-lock on the list of triggers. releaseTriggerListReadLock()
* must be called to release the lock. Called by all primary put and
* delete operations.
*/
private void acquireTriggerListReadLock()
throws DatabaseException {
EnvironmentImpl env = envHandle.getEnvironmentImpl();
env.getTriggerLatch().acquireShared();
if (triggerList == null) {
triggerList = new ArrayList();
}
}
/**
* Releases a lock acquired by calling acquireTriggerListReadLock().
*/
private void releaseTriggerListReadLock()
throws DatabaseException {
EnvironmentImpl env = envHandle.getEnvironmentImpl();
env.getTriggerLatch().release();
}
/**
* Gets a write lock on the list of triggers. An empty list is created if
* necessary, so null is never returned. releaseTriggerListWriteLock()
* must always be called to release the lock.
*/
private void acquireTriggerListWriteLock()
throws DatabaseException {
EnvironmentImpl env = envHandle.getEnvironmentImpl();
env.getTriggerLatch().acquireExclusive();
if (triggerList == null) {
triggerList = new ArrayList();
}
}
/**
* Releases a lock acquired by calling acquireTriggerListWriteLock(). If
* the list is now empty then it is set to null, that is, hasTriggers()
* will subsequently return false.
*/
private void releaseTriggerListWriteLock()
throws DatabaseException {
if (triggerList.size() == 0) {
triggerList = null;
}
EnvironmentImpl env = envHandle.getEnvironmentImpl();
env.getTriggerLatch().release();
}
/**
* Adds a given trigger to the list of triggers. Called while opening
* a SecondaryDatabase.
*
* @param insertAtFront true to insert at the front, or false to append.
*/
void addTrigger(DatabaseTrigger trigger, boolean insertAtFront)
throws DatabaseException {
acquireTriggerListWriteLock();
try {
if (insertAtFront) {
triggerList.add(0, trigger);
} else {
triggerList.add(trigger);
}
trigger.triggerAdded(this);
} finally {
releaseTriggerListWriteLock();
}
}
/**
* Removes a given trigger from the list of triggers. Called by
* SecondaryDatabase.close().
*/
void removeTrigger(DatabaseTrigger trigger)
throws DatabaseException {
acquireTriggerListWriteLock();
try {
triggerList.remove(trigger);
trigger.triggerRemoved(this);
} finally {
releaseTriggerListWriteLock();
}
}
/**
* Clears the list of triggers. Called by close(), this allows closing the
* primary before its secondaries, although we document that secondaries
* should be closed first.
*/
private void removeAllTriggers()
throws DatabaseException {
acquireTriggerListWriteLock();
try {
for (int i = 0; i < triggerList.size(); i += 1) {
DatabaseTrigger trigger = (DatabaseTrigger) triggerList.get(i);
trigger.triggerRemoved(this);
}
triggerList.clear();
} finally {
releaseTriggerListWriteLock();
}
}
/**
* Notifies associated triggers when a put() or delete() is performed on
* the primary. This method is normally called only if hasTriggers() has
* returned true earlier. This avoids acquiring a shared latch for
* primaries with no triggers. If a trigger is added during the update
* process, there is no requirement to immediately start updating it.
*
* @param locker the internal locker.
*
* @param priKey the primary key.
*
* @param oldData the primary data before the change, or null if the record
* did not previously exist.
*
* @param newData the primary data after the change, or null if the record
* has been deleted.
*/
void notifyTriggers(Locker locker,
DatabaseEntry priKey,
DatabaseEntry oldData,
DatabaseEntry newData)
throws DatabaseException {
acquireTriggerListReadLock();
try {
for (int i = 0; i < triggerList.size(); i += 1) {
DatabaseTrigger trigger = (DatabaseTrigger) triggerList.get(i);
/* Notify trigger. */
trigger.databaseUpdated
(this, locker, priKey, oldData, newData);
}
} finally {
releaseTriggerListReadLock();
}
}
}