package com.sleepycat.je.dbi;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DatabaseNotFoundException;
import com.sleepycat.je.DeadlockException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.je.dbi.CursorImpl.SearchMode;
import com.sleepycat.je.log.LogEntryType;
import com.sleepycat.je.log.LogException;
import com.sleepycat.je.log.LogReadable;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.LoggableObject;
import com.sleepycat.je.tree.ChildReference;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.tree.MapLN;
import com.sleepycat.je.tree.NameLN;
import com.sleepycat.je.tree.Tree;
import com.sleepycat.je.tree.TreeUtils;
import com.sleepycat.je.tree.WithRootLatched;
import com.sleepycat.je.txn.AutoTxn;
import com.sleepycat.je.txn.BasicLocker;
import com.sleepycat.je.txn.LockType;
import com.sleepycat.je.txn.Locker;
import de.ovgu.cide.jakutil.*;
/**
* Represents the DatabaseImpl Naming Tree.
*/
public class DbTree implements LoggableObject, LogReadable {
public static final DatabaseId ID_DB_ID=new DatabaseId(0);
public static final DatabaseId NAME_DB_ID=new DatabaseId(1);
public static final String ID_DB_NAME="_jeIdMap";
public static final String NAME_DB_NAME="_jeNameMap";
public static final String UTILIZATION_DB_NAME="_jeUtilization";
private static final String[] RESERVED_DB_NAMES={ID_DB_NAME,NAME_DB_NAME,UTILIZATION_DB_NAME};
private int lastAllocatedDbId;
private DatabaseImpl idDatabase;
private DatabaseImpl nameDatabase;
private EnvironmentImpl envImpl;
/**
* Create a dbTree from the log.
*/
public DbTree() throws DatabaseException {
this.envImpl=null;
idDatabase=new DatabaseImpl();
idDatabase.setDebugDatabaseName(ID_DB_NAME);
nameDatabase=new DatabaseImpl();
nameDatabase.setDebugDatabaseName(NAME_DB_NAME);
}
/**
* Create a new dbTree for a new environment.
*/
public DbTree( EnvironmentImpl env) throws DatabaseException {
this.envImpl=env;
idDatabase=new DatabaseImpl(ID_DB_NAME,new DatabaseId(0),env,new DatabaseConfig());
nameDatabase=new DatabaseImpl(NAME_DB_NAME,new DatabaseId(1),env,new DatabaseConfig());
lastAllocatedDbId=1;
}
/**
* Get the latest allocated id, for checkpoint info.
*/
public synchronized int getLastDbId(){
return lastAllocatedDbId;
}
/**
* Get the next available database id.
*/
private synchronized int getNextDbId(){
return ++lastAllocatedDbId;
}
/**
* Initialize the db id, from recovery.
*/
public synchronized void setLastDbId( int maxDbId){
lastAllocatedDbId=maxDbId;
}
private Locker createLocker( EnvironmentImpl envImpl) throws DatabaseException {
if (envImpl.isNoLocking()) {
return new BasicLocker(envImpl);
}
else {
return new AutoTxn(envImpl,new TransactionConfig());
}
}
/**
* Set the db environment during recovery, after instantiating the tree from
* the log.
*/
void setEnvironmentImpl( EnvironmentImpl envImpl) throws DatabaseException {
this.envImpl=envImpl;
idDatabase.setEnvironmentImpl(envImpl);
nameDatabase.setEnvironmentImpl(envImpl);
}
/**
* Create a database.
*/
public synchronized DatabaseImpl createDb( Locker locker, String databaseName, DatabaseConfig dbConfig, Database databaseHandle) throws DatabaseException {
return createDb(locker,databaseName,dbConfig,databaseHandle,true);
}
/**
* Create a database.
* @param lockerowning locker
* @param databaseNameidentifier for database
* @param dbConfig
* @param allowEvictionis whether eviction is allowed during cursor operations.
*/
public synchronized DatabaseImpl createDb( Locker locker, String databaseName, DatabaseConfig dbConfig, Database databaseHandle, boolean allowEviction) throws DatabaseException {
DatabaseId newId=new DatabaseId(getNextDbId());
DatabaseImpl newDb=new DatabaseImpl(databaseName,newId,envImpl,dbConfig);
CursorImpl idCursor=null;
CursorImpl nameCursor=null;
boolean operationOk=false;
Locker autoTxn=null;
try {
nameCursor=new CursorImpl(nameDatabase,locker);
this.hook307(allowEviction,nameCursor);
LN nameLN=new NameLN(newId);
nameCursor.putLN(databaseName.getBytes("UTF-8"),nameLN,false);
if (databaseHandle != null) {
locker.addToHandleMaps(new Long(nameLN.getNodeId()),databaseHandle);
}
autoTxn=createLocker(envImpl);
idCursor=new CursorImpl(idDatabase,autoTxn);
this.hook306(allowEviction,idCursor);
idCursor.putLN(newId.getBytes(),new MapLN(newDb),false);
operationOk=true;
}
catch ( UnsupportedEncodingException UEE) {
throw new DatabaseException(UEE);
}
finally {
if (idCursor != null) {
idCursor.close();
}
if (nameCursor != null) {
nameCursor.close();
}
if (autoTxn != null) {
autoTxn.operationEnd(operationOk);
}
}
return newDb;
}
/**
* Called by the Tree to propagate a root change. If the tree is a data
* database, we will write the MapLn that represents this db to the log. If
* the tree is one of the mapping dbs, we'll write the dbtree to the log.
* @param dbthe target db
*/
public void modifyDbRoot( DatabaseImpl db) throws DatabaseException {
if (db.getId().equals(ID_DB_ID) || db.getId().equals(NAME_DB_ID)) {
envImpl.logMapTreeRoot();
}
else {
Locker locker=createLocker(envImpl);
CursorImpl cursor=new CursorImpl(idDatabase,locker);
boolean operationOk=false;
try {
DatabaseEntry keyDbt=new DatabaseEntry(db.getId().getBytes());
MapLN mapLN=null;
while (true) {
try {
boolean searchOk=(cursor.searchAndPosition(keyDbt,new DatabaseEntry(),SearchMode.SET,LockType.WRITE) & CursorImpl.FOUND) != 0;
if (!searchOk) {
throw new DatabaseException("can't find database " + db.getId());
}
mapLN=(MapLN)cursor.getCurrentLNAlreadyLatched(LockType.WRITE);
assert mapLN != null;
}
catch ( DeadlockException DE) {
cursor.close();
locker.operationEnd(false);
locker=createLocker(envImpl);
cursor=new CursorImpl(idDatabase,locker);
continue;
}
finally {
this.hook299(cursor);
}
break;
}
RewriteMapLN writeMapLN=new RewriteMapLN(cursor);
mapLN.getDatabase().getTree().withRootLatchedExclusive(writeMapLN);
operationOk=true;
}
finally {
if (cursor != null) {
cursor.close();
}
locker.operationEnd(operationOk);
}
}
}
private static class RewriteMapLN implements WithRootLatched {
private CursorImpl cursor;
RewriteMapLN( CursorImpl cursor){
this.cursor=cursor;
}
public IN doWork( ChildReference root) throws DatabaseException {
DatabaseEntry dataDbt=new DatabaseEntry(new byte[0]);
cursor.putCurrent(dataDbt,null,null);
return null;
}
}
private NameLockResult lockNameLN( Locker locker, String databaseName, String action) throws DatabaseException {
NameLockResult result=new NameLockResult();
result.dbImpl=getDb(locker,databaseName,null);
if (result.dbImpl == null) {
throw new DatabaseNotFoundException("Attempted to " + action + " non-existent database "+ databaseName);
}
result.nameCursor=new CursorImpl(nameDatabase,locker);
try {
DatabaseEntry key=new DatabaseEntry(databaseName.getBytes("UTF-8"));
boolean found=(result.nameCursor.searchAndPosition(key,null,SearchMode.SET,LockType.WRITE) & CursorImpl.FOUND) != 0;
if (!found) {
this.hook300(result);
result.nameCursor.close();
result.nameCursor=null;
return result;
}
result.nameLN=(NameLN)result.nameCursor.getCurrentLNAlreadyLatched(LockType.WRITE);
assert result.nameLN != null;
int handleCount=result.dbImpl.getReferringHandleCount();
if (handleCount > 0) {
throw new DatabaseException("Can't " + action + " database "+ databaseName+ ","+ handleCount+ " open Dbs exist");
}
}
catch ( UnsupportedEncodingException UEE) {
this.hook301(result);
result.nameCursor.close();
throw new DatabaseException(UEE);
}
catch ( DatabaseException e) {
this.hook302(result);
result.nameCursor.close();
throw e;
}
return result;
}
private static class NameLockResult {
CursorImpl nameCursor;
DatabaseImpl dbImpl;
NameLN nameLN;
}
void deleteMapLN( DatabaseId id) throws DatabaseException {
Locker autoTxn=null;
boolean operationOk=false;
CursorImpl idCursor=null;
try {
autoTxn=createLocker(envImpl);
idCursor=new CursorImpl(idDatabase,autoTxn);
boolean found=(idCursor.searchAndPosition(new DatabaseEntry(id.getBytes()),null,SearchMode.SET,LockType.WRITE) & CursorImpl.FOUND) != 0;
if (found) {
idCursor.delete();
}
operationOk=true;
}
finally {
if (idCursor != null) {
idCursor.close();
}
if (autoTxn != null) {
autoTxn.operationEnd(operationOk);
}
}
}
/**
* Get a database object given a database name.
*/
public DatabaseImpl getDb( Locker nameLocker, String databaseName, Database databaseHandle) throws DatabaseException {
return getDb(nameLocker,databaseName,databaseHandle,true);
}
/**
* Get a database object given a database name.
* @param nameLockeris used to access the NameLN. As always, a NullTxn is used to
* access the MapLN.
* @param databaseNametarget database
* @return null if database doesn't exist
* @param allowEvictionis whether eviction is allowed during cursor operations.
*/
public DatabaseImpl getDb( Locker nameLocker, String databaseName, Database databaseHandle, boolean allowEviction) throws DatabaseException {
try {
CursorImpl nameCursor=null;
DatabaseId id=null;
try {
nameCursor=new CursorImpl(nameDatabase,nameLocker);
this.hook308(allowEviction,nameCursor);
DatabaseEntry keyDbt=new DatabaseEntry(databaseName.getBytes("UTF-8"));
boolean found=(nameCursor.searchAndPosition(keyDbt,null,SearchMode.SET,LockType.READ) & CursorImpl.FOUND) != 0;
if (found) {
NameLN nameLN=(NameLN)nameCursor.getCurrentLNAlreadyLatched(LockType.READ);
assert nameLN != null;
id=nameLN.getId();
if (databaseHandle != null) {
nameLocker.addToHandleMaps(new Long(nameLN.getNodeId()),databaseHandle);
}
}
}
finally {
if (nameCursor != null) {
this.hook303(nameCursor);
nameCursor.close();
}
}
if (id == null) {
return null;
}
else {
return getDb(id,-1,allowEviction,databaseName);
}
}
catch ( UnsupportedEncodingException UEE) {
throw new DatabaseException(UEE);
}
}
/**
* Get a database object based on an id only. Used by recovery, cleaning and
* other clients who have an id in hand, and don't have a resident node, to
* find the matching database for a given log entry.
*/
public DatabaseImpl getDb( DatabaseId dbId) throws DatabaseException {
return getDb(dbId,-1);
}
/**
* Get a database object based on an id only. Specify the lock timeout to
* use, or -1 to use the default timeout. A timeout should normally only be
* specified by daemons with their own timeout configuration. public for
* unit tests.
*/
public DatabaseImpl getDb( DatabaseId dbId, long lockTimeout) throws DatabaseException {
return getDb(dbId,lockTimeout,true,null);
}
/**
* Get a database object based on an id only, caching the id-db mapping in
* the given map.
*/
public DatabaseImpl getDb( DatabaseId dbId, long lockTimeout, Map dbCache) throws DatabaseException {
if (dbCache.containsKey(dbId)) {
return (DatabaseImpl)dbCache.get(dbId);
}
else {
DatabaseImpl db=getDb(dbId,lockTimeout,true,null);
dbCache.put(dbId,db);
return db;
}
}
/**
* Get a database object based on an id only. Specify the lock timeout to
* use, or -1 to use the default timeout. A timeout should normally only be
* specified by daemons with their own timeout configuration. public for
* unit tests.
* @param allowEvictionis whether eviction is allowed during cursor operations.
*/
public DatabaseImpl getDb( DatabaseId dbId, long lockTimeout, boolean allowEviction, String dbNameIfAvailable) throws DatabaseException {
if (dbId.equals(idDatabase.getId())) {
return idDatabase;
}
else if (dbId.equals(nameDatabase.getId())) {
return nameDatabase;
}
else {
Locker locker=new BasicLocker(envImpl);
if (lockTimeout != -1) {
locker.setLockTimeout(lockTimeout);
}
CursorImpl idCursor=null;
DatabaseImpl foundDbImpl=null;
while (true) {
idCursor=new CursorImpl(idDatabase,locker);
this.hook309(allowEviction,idCursor);
try {
DatabaseEntry keyDbt=new DatabaseEntry(dbId.getBytes());
boolean found=(idCursor.searchAndPosition(keyDbt,new DatabaseEntry(),SearchMode.SET,LockType.READ) & CursorImpl.FOUND) != 0;
if (found) {
MapLN mapLN=(MapLN)idCursor.getCurrentLNAlreadyLatched(LockType.READ);
assert mapLN != null;
foundDbImpl=mapLN.getDatabase();
}
break;
}
catch ( DeadlockException DE) {
idCursor.close();
locker.operationEnd(false);
locker=new BasicLocker(envImpl);
if (lockTimeout != -1) {
locker.setLockTimeout(lockTimeout);
}
idCursor=new CursorImpl(idDatabase,locker);
this.hook310(allowEviction,idCursor);
continue;
}
finally {
this.hook304(idCursor);
idCursor.close();
locker.operationEnd(true);
}
}
if (envImpl.isOpen()) {
setDebugNameForDatabaseImpl(foundDbImpl,dbNameIfAvailable);
}
return foundDbImpl;
}
}
private void setDebugNameForDatabaseImpl( DatabaseImpl dbImpl, String dbName) throws DatabaseException {
if (dbImpl != null) {
if (dbName != null) {
dbImpl.setDebugDatabaseName(dbName);
}
else if (dbImpl.getDebugName() == null) {
dbImpl.setDebugDatabaseName(getDbName(dbImpl.getId()));
}
}
}
/**
* Rebuild the IN list after recovery.
*/
public void rebuildINListMapDb() throws DatabaseException {
idDatabase.getTree().rebuildINList();
}
/**
* Return the database name for a given db. Slow, must traverse. Used by
* truncate and for debugging.
*/
public String getDbName( DatabaseId id) throws DatabaseException {
if (id.equals(ID_DB_ID)) {
return ID_DB_NAME;
}
else if (id.equals(NAME_DB_ID)) {
return NAME_DB_NAME;
}
Locker locker=null;
CursorImpl cursor=null;
try {
locker=new BasicLocker(envImpl);
cursor=new CursorImpl(nameDatabase,locker);
DatabaseEntry keyDbt=new DatabaseEntry();
DatabaseEntry dataDbt=new DatabaseEntry();
String name=null;
if (cursor.positionFirstOrLast(true,null)) {
OperationStatus status=cursor.getCurrentAlreadyLatched(keyDbt,dataDbt,LockType.NONE,true);
do {
if (status == OperationStatus.SUCCESS) {
NameLN nameLN=(NameLN)cursor.getCurrentLN(LockType.NONE);
if (nameLN != null && nameLN.getId().equals(id)) {
name=new String(keyDbt.getData(),"UTF-8");
break;
}
}
status=cursor.getNext(keyDbt,dataDbt,LockType.NONE,true,false);
}
while (status == OperationStatus.SUCCESS);
}
return name;
}
catch ( UnsupportedEncodingException UEE) {
throw new DatabaseException(UEE);
}
finally {
if (cursor != null) {
this.hook305(cursor);
cursor.close();
}
if (locker != null) {
locker.operationEnd();
}
}
}
/**
* @return a list of database names held in the environment, as strings.
*/
public List getDbNames() throws DatabaseException {
List nameList=new ArrayList();
Locker locker=null;
CursorImpl cursor=null;
try {
locker=new BasicLocker(envImpl);
cursor=new CursorImpl(nameDatabase,locker);
DatabaseEntry keyDbt=new DatabaseEntry();
DatabaseEntry dataDbt=new DatabaseEntry();
if (cursor.positionFirstOrLast(true,null)) {
OperationStatus status=cursor.getCurrentAlreadyLatched(keyDbt,dataDbt,LockType.READ,true);
do {
if (status == OperationStatus.SUCCESS) {
String name=new String(keyDbt.getData(),"UTF-8");
if (!isReservedDbName(name)) {
nameList.add(name);
}
}
status=cursor.getNext(keyDbt,dataDbt,LockType.READ,true,false);
}
while (status == OperationStatus.SUCCESS);
}
return nameList;
}
catch ( UnsupportedEncodingException UEE) {
throw new DatabaseException(UEE);
}
finally {
if (cursor != null) {
cursor.close();
}
if (locker != null) {
locker.operationEnd();
}
}
}
/**
* Returns true if the name is a reserved JE database name.
*/
public boolean isReservedDbName( String name){
for (int i=0; i < RESERVED_DB_NAMES.length; i+=1) {
if (RESERVED_DB_NAMES[i].equals(name)) {
return true;
}
}
return false;
}
/**
* @return the higest level node in the environment.
*/
public int getHighestLevel() throws DatabaseException {
RootLevel getLevel=new RootLevel(idDatabase);
idDatabase.getTree().withRootLatchedShared(getLevel);
int idHighLevel=getLevel.getRootLevel();
getLevel=new RootLevel(nameDatabase);
nameDatabase.getTree().withRootLatchedShared(getLevel);
int nameHighLevel=getLevel.getRootLevel();
return (nameHighLevel > idHighLevel) ? nameHighLevel : idHighLevel;
}
private static class RootLevel implements WithRootLatched {
private DatabaseImpl db;
private int rootLevel;
RootLevel( DatabaseImpl db){
this.db=db;
rootLevel=0;
}
public IN doWork( ChildReference root) throws DatabaseException {
IN rootIN=(IN)root.fetchTarget(db,null);
rootLevel=rootIN.getLevel();
return null;
}
int getRootLevel(){
return rootLevel;
}
}
/**
* @see LoggableObject#getLogType
*/
public LogEntryType getLogType(){
return LogEntryType.LOG_ROOT;
}
/**
* @see LoggableObject#marshallOutsideWriteLatch Can be marshalled outside
* the log write latch.
*/
public boolean marshallOutsideWriteLatch(){
return true;
}
/**
* @see LoggableObject#countAsObsoleteWhenLogged
*/
public boolean countAsObsoleteWhenLogged(){
return false;
}
/**
* @see LoggableObject#getLogSize
*/
public int getLogSize(){
return LogUtils.getIntLogSize() + idDatabase.getLogSize() + nameDatabase.getLogSize();
}
/**
* @see LoggableObject#writeToLog
*/
public void writeToLog( ByteBuffer logBuffer){
LogUtils.writeInt(logBuffer,lastAllocatedDbId);
idDatabase.writeToLog(logBuffer);
nameDatabase.writeToLog(logBuffer);
}
/**
* @see LoggableObject#postLogWork
*/
public void postLogWork( long justLoggedLsn) throws DatabaseException {
}
/**
* @see LogReadable#readFromLog
*/
public void readFromLog( ByteBuffer itemBuffer, byte entryTypeVersion) throws LogException {
lastAllocatedDbId=LogUtils.readInt(itemBuffer);
idDatabase.readFromLog(itemBuffer,entryTypeVersion);
nameDatabase.readFromLog(itemBuffer,entryTypeVersion);
}
/**
* @see LogReadable#dumpLog
*/
public void dumpLog( StringBuffer sb, boolean verbose){
sb.append("<dbtree lastId = \"");
sb.append(lastAllocatedDbId);
sb.append("\">");
sb.append("<idDb>");
idDatabase.dumpLog(sb,verbose);
sb.append("</idDb><nameDb>");
nameDatabase.dumpLog(sb,verbose);
sb.append("</nameDb>");
sb.append("</dbtree>");
}
/**
* @see LogReadable#logEntryIsTransactional.
*/
public boolean logEntryIsTransactional(){
return false;
}
/**
* @see LogReadable#getTransactionId
*/
public long getTransactionId(){
return 0;
}
String dumpString( int nSpaces){
StringBuffer self=new StringBuffer();
self.append(TreeUtils.indent(nSpaces));
self.append("<dbTree lastDbId =\"");
self.append(lastAllocatedDbId);
self.append("\">");
self.append('\n');
self.append(idDatabase.dumpString(nSpaces + 1));
self.append('\n');
self.append(nameDatabase.dumpString(nSpaces + 1));
self.append('\n');
self.append("</dbtree>");
return self.toString();
}
public String toString(){
return dumpString(0);
}
/**
* For debugging.
*/
public void dump() throws DatabaseException {
idDatabase.getTree().dump();
nameDatabase.getTree().dump();
}
protected void hook299( CursorImpl cursor) throws DatabaseException {
}
protected void hook300( NameLockResult result) throws DatabaseException, UnsupportedEncodingException {
}
protected void hook301( NameLockResult result) throws DatabaseException {
}
protected void hook302( NameLockResult result) throws DatabaseException {
}
protected void hook303( CursorImpl nameCursor) throws DatabaseException, UnsupportedEncodingException {
}
protected void hook304( CursorImpl idCursor) throws DatabaseException {
}
protected void hook305( CursorImpl cursor) throws DatabaseException {
}
protected void hook306( boolean allowEviction, CursorImpl idCursor) throws DatabaseException, UnsupportedEncodingException {
}
protected void hook307( boolean allowEviction, CursorImpl nameCursor) throws DatabaseException, UnsupportedEncodingException {
}
protected void hook308( boolean allowEviction, CursorImpl nameCursor) throws DatabaseException, UnsupportedEncodingException {
}
protected void hook309( boolean allowEviction, CursorImpl idCursor) throws DatabaseException {
}
protected void hook310( boolean allowEviction, CursorImpl idCursor) throws DatabaseException {
}
}