package com.sleepycat.je; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; 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.CursorImpl.SearchMode; import com.sleepycat.je.txn.Locker; import com.sleepycat.je.txn.LockerFactory; import com.sleepycat.je.utilint.TinyHashSet; import com.sleepycat.je.utilint.Tracer; import de.ovgu.cide.jakutil.*; public class Database { 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"); private DbState state; Environment envHandle; private DatabaseImpl databaseImpl; DatabaseConfig configuration; private boolean isWritable; Locker handleLocker; private TinyHashSet cursors=new TinyHashSet(); 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); 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 { validateConfigAgainstExistingDb(dbConfig,databaseImpl); init(env,dbConfig); this.databaseImpl=databaseImpl; databaseImpl.addReferringHandle(this); 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 { 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()+ "."); } } 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()); } 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:"); this.hook44(); 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"); Iterator iter=cursors.copy().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; 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"); this.hook45(txn,key); 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"); } this.hook46(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"); this.hook47(txn,key); 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); cursor.setNonCloning(true); OperationStatus commitStatus=OperationStatus.NOTFOUND; DatabaseEntry oldData=new DatabaseEntry(); OperationStatus searchStatus=cursor.search(key,oldData,LockMode.RMW,SearchMode.SET); if (searchStatus == OperationStatus.SUCCESS) { do { if (hasTriggers()) { notifyTriggers(locker,key,oldData,null); } commitStatus=cursor.deleteNoNotify(); if (commitStatus != OperationStatus.SUCCESS) { return commitStatus; } 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:"); this.hook48(txn,key,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); cursor.setNonCloning(true); 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:"); this.hook49(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); cursor.setNonCloning(true); 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"); this.hook50(txn,key,data); 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"); this.hook51(txn,key,data); 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"); this.hook52(txn,key,data); 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); cursor.setNonCloning(true); 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."); } 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; } 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."); } } } return new JoinCursor(locker,this,cursors,config); } public void preload( long maxBytes) throws DatabaseException { checkEnv(); checkRequiredDbState(OPEN,"Can't call Database.preload"); this.hook55(); PreloadConfig config=new PreloadConfig(); config.setMaxBytes(maxBytes); databaseImpl.preload(config); } public void preload( long maxBytes, long maxMillisecs) throws DatabaseException { checkEnv(); checkRequiredDbState(OPEN,"Can't call Database.preload"); this.hook56(); PreloadConfig config=new PreloadConfig(); config.setMaxBytes(maxBytes); config.setMaxMillisecs(maxMillisecs); databaseImpl.preload(config); } public PreloadStats preload( PreloadConfig config) throws DatabaseException { checkEnv(); checkRequiredDbState(OPEN,"Can't call Database.preload"); this.hook57(); return databaseImpl.preload(config); } public String getDatabaseName() throws DatabaseException { checkEnv(); if (databaseImpl != null) { return databaseImpl.getName(); } else { return null; } } String getDebugName(){ if (databaseImpl != null) { return databaseImpl.getDebugName(); } else { return null; } } public DatabaseConfig getConfig() throws DatabaseException { DatabaseConfig showConfig=configuration.cloneConfig(); 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(); this.hook53(list); } else { } return list; } /** * @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); } } /** * 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 { new Database_acquireTriggerListReadLock(this).execute(); } /** * 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 { new Database_acquireTriggerListWriteLock(this).execute(); } /** * 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 { new Database_releaseTriggerListWriteLock(this).execute(); } /** * 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(); this.hook54(locker,priKey,oldData,newData); } @MethodObject static class Database_acquireTriggerListReadLock { Database_acquireTriggerListReadLock( Database _this){ this._this=_this; } void execute() throws DatabaseException { if (_this.triggerList == null) { _this.triggerList=new ArrayList(); } } protected Database _this; protected EnvironmentImpl env; } @MethodObject static class Database_acquireTriggerListWriteLock { Database_acquireTriggerListWriteLock( Database _this){ this._this=_this; } void execute() throws DatabaseException { if (_this.triggerList == null) { _this.triggerList=new ArrayList(); } } protected Database _this; protected EnvironmentImpl env; } @MethodObject static class Database_releaseTriggerListWriteLock { Database_releaseTriggerListWriteLock( Database _this){ this._this=_this; } void execute() throws DatabaseException { if (_this.triggerList.size() == 0) { _this.triggerList=null; } } protected Database _this; protected EnvironmentImpl env; } protected void hook44() throws DatabaseException { } protected void hook45( Transaction txn, DatabaseEntry key) throws DatabaseException { } protected void hook46( Transaction txn, CursorConfig cursorConfig) throws DatabaseException { } protected void hook47( Transaction txn, DatabaseEntry key) throws DatabaseException { } protected void hook48( Transaction txn, DatabaseEntry key, LockMode lockMode) throws DatabaseException { } protected void hook49( Transaction txn, DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { } protected void hook50( Transaction txn, DatabaseEntry key, DatabaseEntry data) throws DatabaseException { } protected void hook51( Transaction txn, DatabaseEntry key, DatabaseEntry data) throws DatabaseException { } protected void hook52( Transaction txn, DatabaseEntry key, DatabaseEntry data) throws DatabaseException { } protected void hook53( List list) throws DatabaseException { for (int i=0; i < triggerList.size(); i+=1) { Object obj=triggerList.get(i); if (obj instanceof SecondaryTrigger) { list.add(((SecondaryTrigger)obj).getDb()); } } } protected void hook54( Locker locker, DatabaseEntry priKey, DatabaseEntry oldData, DatabaseEntry newData) throws DatabaseException { for (int i=0; i < triggerList.size(); i+=1) { DatabaseTrigger trigger=(DatabaseTrigger)triggerList.get(i); trigger.databaseUpdated(this,locker,priKey,oldData,newData); } } protected void hook55() throws DatabaseException { } protected void hook56() throws DatabaseException { } protected void hook57() throws DatabaseException { } }