/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.backends.jeb; import org.opends.server.api.DirectoryThread; import com.sleepycat.je.*; import org.opends.server.types.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.loggers.debug.DebugLogger.debugError; import static org.opends.server.messages.JebMessages. MSGID_JEB_MISSING_DN2ID_RECORD; import static org.opends.server.messages.JebMessages. MSGID_JEB_REBUILD_INDEX_FAILED; import static org.opends.server.messages.JebMessages. MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED; import static org.opends.server.messages.MessageHandler.getMessage; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; /** * A thread to do the actual work of rebuilding an index. */ public class IndexRebuildThread extends DirectoryThread { /** * The entry container. */ EntryContainer ec = null; /** * The internal database/indexType to rebuild. */ IndexType indexType = null; /** * The attribute indexType to rebuild. */ AttributeIndex attrIndex = null; /** * The indexType to rebuild. */ Index index = null; /** * Name of the indexType being rebuilt. */ String indexName = null; /** * The ID2ENTRY database. */ ID2Entry id2entry = null; /** * The number of total entries to rebuild. An negative value indicates this * value is not yet known. */ long totalEntries = -1; /** * The number of entries processed. */ long processedEntries = 0; /** * The number of entries rebuilt successfully. */ long rebuiltEntries = 0; /** * The number of entries rebuilt with possible duplicates. */ long duplicatedEntries = 0; /** * The number of entries that were skipped because they were not applicable * for the indexType or because an error occured. */ long skippedEntries = 0; /** * The types of internal indexes that are rebuildable. */ enum IndexType { DN2ID, DN2URI, ID2CHILDREN, ID2SUBTREE, INDEX, ATTRIBUTEINDEX } /** * Construct a new index rebuild thread to rebuild a system index. * * @param ec The entry container to rebuild in. * @param index The index type to rebuild. */ IndexRebuildThread(EntryContainer ec, IndexType index) { super("Index Rebuild Thread " + ec.getContainerName() + "_" + index.toString()); this.ec = ec; this.indexType = index; this.id2entry = ec.getID2Entry(); this.indexName = ec.getContainerName() + "_" + index.toString(); } /** * Construct a new index rebuild thread to rebuild an index. * * @param ec The entry container to rebuild in. * @param index The index to rebuild. */ IndexRebuildThread(EntryContainer ec, Index index) { super("Index Rebuild Thread " + ec.getContainerName() + "_" + index.toString()); this.ec = ec; this.indexType = IndexType.INDEX; this.index = index; this.id2entry = ec.getID2Entry(); this.indexName = ec.getContainerName() + "_" + index.toString(); } /** * Construct a new index rebuild thread to rebuild an attribute index. * * @param ec The entry container to rebuild in. * @param index The attribute index to rebuild. */ IndexRebuildThread(EntryContainer ec, AttributeIndex index) { super("Index Rebuild Thread " + ec.getContainerName() + "_" + index.toString()); this.ec = ec; this.indexType = IndexType.ATTRIBUTEINDEX; this.attrIndex = index; this.id2entry = ec.getID2Entry(); this.indexName = ec.getContainerName() + "_" + index.toString(); } /** * Start the rebuild process. */ public void run() { if(indexType == null) { //TODO: throw error if(debugEnabled()) { debugError("No index type specified. Rebuild process terminated."); } return; } if(indexType == IndexType.ATTRIBUTEINDEX && attrIndex == null) { //TODO: throw error if(debugEnabled()) { debugError("No attribute index specified. Rebuild process terminated."); } return; } if(indexType == IndexType.INDEX && index == null) { //TODO: throw error if(debugEnabled()) { debugError("No index specified. Rebuild process terminated."); } return; } try { totalEntries = getTotalEntries(); if(debugEnabled()) { debugInfo("Initiating rebuild of the %s indexType/database", indexName); debugVerbose("%d entries will be rebuilt", totalEntries); } switch(indexType) { case DN2ID : rebuildDN2ID(); break; case DN2URI : rebuildDN2URI(); break; case ID2CHILDREN : rebuildID2Children(); break; case ID2SUBTREE : rebuildID2Subtree(); break; case ATTRIBUTEINDEX : rebuildAttributeIndex(attrIndex); break; case INDEX : rebuildAttributeIndex(index); } if(debugEnabled()) { debugVerbose("Rebuilt %d entries", rebuiltEntries); } } catch(Exception e) { int msgID = MSGID_JEB_REBUILD_INDEX_FAILED; String message = getMessage(msgID, this.getName(), stackTraceToSingleLineString(e)); logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR, message, msgID); if(debugEnabled()) { debugCaught(DebugLogLevel.ERROR, e); } } } /** * Rebuild an interal DN2ID database. * * @throws DatabaseException If an error occurs during the rebuild. */ private void rebuildDN2ID() throws DatabaseException { DN2ID dn2id = ec.getDN2ID(); Transaction txn = ec.beginTransaction(); try { //Delete all records in the indexType databases. //TODO: Should we do a transactional delete? dn2id.clear(txn); } finally { EntryContainer.transactionCommit(txn); } //Iterate through the id2entry database and insert associated dn2id //records. Cursor cursor = id2entry.openCursor(null, null); try { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); LockMode lockMode = LockMode.DEFAULT; OperationStatus status; for (status = cursor.getFirst(key, data, lockMode); status == OperationStatus.SUCCESS; status = cursor.getNext(key, data, lockMode)) { try { EntryID entryID = new EntryID(key); Entry entry = JebFormat.entryFromDatabase(data.getData()); //TODO: Should we add all records in a big transaction? //TODO: Should we make each insert a transaction? // Insert into dn2id. if (dn2id.insert(null, entry.getDN(), entryID)) { rebuiltEntries++; } else { // The entry ID alreadly exists in the database. // This could happen if some other process got to this entry // before we did. Since the backend should be offline, this // might be a problem. duplicatedEntries++; if(debugEnabled()) { debugInfo("Unable to insert entry with DN %s and ID %d " + "into the DN2ID database because it already exists.", entry.getDN().toString(), entryID.longValue()); } } processedEntries++; } catch (Exception e) { //TODO: throw error stating that the indexType could be in an // inconsistant state. //TODO: Should we continue on or stop right now? skippedEntries++; int msgID = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED; String message = getMessage(msgID, indexName, stackTraceToSingleLineString(e)); logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.MILD_ERROR, message, msgID); if (debugEnabled()) { debugCaught(DebugLogLevel.ERROR, e); } } } } finally { cursor.close(); } } /** * Rebuild the ID2URI internal database. * * @throws DatabaseException if an error occurs during rebuild. */ private void rebuildDN2URI() throws DatabaseException { DN2URI dn2uri = ec.getDN2URI(); Transaction txn = ec.beginTransaction(); try { //Delete all records in the indexType databases. //TODO: Should we do a transactional delete? dn2uri.clear(txn); } finally { EntryContainer.transactionCommit(txn); } //Iterate through the id2entry database and insert associated dn2uri //records. Cursor cursor = id2entry.openCursor(null, null); try { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); LockMode lockMode = LockMode.DEFAULT; OperationStatus status; for (status = cursor.getFirst(key, data, lockMode); status == OperationStatus.SUCCESS; status = cursor.getNext(key, data, lockMode)) { try { EntryID entryID = new EntryID(key); Entry entry = JebFormat.entryFromDatabase(data.getData()); //TODO: Should we add all records in a big transaction? //TODO: Should we make each insert a transaction? // Insert into dn2uri. if (dn2uri.addEntry(null, entry)) { rebuiltEntries++; } else { // The entry DN and URIs alreadly exists in the database. // This could happen if some other process got to this entry // before we did. Since the backend should be offline, this // might be a problem. duplicatedEntries++; if(debugEnabled()) { debugInfo("Unable to insert entry with DN %s and ID %d " + "into the DN2URI database because it already exists.", entry.getDN().toString(), entryID.longValue()); } } processedEntries++; } catch (Exception e) { //TODO: throw error stating that the indexType could be in an // inconsistant state. //TODO: Should we continue on or stop right now? skippedEntries++; int msgID = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED; String message = getMessage(msgID, indexName, stackTraceToSingleLineString(e)); logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.MILD_ERROR, message, msgID); if (debugEnabled()) { debugCaught(DebugLogLevel.ERROR, e); } } } } finally { cursor.close(); } } /** * Rebuild the ID2Subtree internal index. This depends on the DN2ID and DN2URI * databases being complete. * * @throws DatabaseException if an error occurs during rebuild. */ private void rebuildID2Children() throws DatabaseException { Index id2children = ec.getID2Children(); Transaction txn = ec.beginTransaction(); try { //Delete all records in the indexType databases. //TODO: Should we do a transactional delete? id2children.clear(txn); } finally { EntryContainer.transactionCommit(txn); } DN2ID dn2id = ec.getDN2ID(); DN2URI dn2uri = ec.getDN2URI(); //Iterate through the id2entry database and insert associated dn2children //records. Cursor cursor = id2entry.openCursor(null, null); try { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); LockMode lockMode = LockMode.DEFAULT; OperationStatus status; for (status = cursor.getFirst(key, data, lockMode); status == OperationStatus.SUCCESS; status = cursor.getNext(key, data, lockMode)) { try { EntryID entryID = new EntryID(key); Entry entry = JebFormat.entryFromDatabase(data.getData()); // Check that the parent entry exists. DN parentDN = ec.getParentWithinBase(entry.getDN()); if (parentDN != null) { // Check for referral entries above the target. dn2uri.targetEntryReferrals(entry.getDN(), null); // Read the parent ID from dn2id. EntryID parentID = dn2id.get(null, parentDN); if (parentID != null) { // Insert into id2children for parent ID. if(id2children.insertID(null, parentID.getDatabaseEntry(), entryID)) { rebuiltEntries++; } else { // The entry alreadly exists in the database. // This could happen if some other process got to this entry // before we did. Since the backend should be offline, this // might be a problem. if(debugEnabled()) { duplicatedEntries++; debugInfo("Unable to insert entry with DN %s and ID %d " + "into the DN2Subtree database because it already " + "exists.", entry.getDN().toString(), entryID.longValue()); } } } else { int msgID = MSGID_JEB_MISSING_DN2ID_RECORD; String msg = getMessage(msgID, parentDN.toNormalizedString()); throw new JebException(msgID, msg); } } else { skippedEntries++; } processedEntries++; } catch (Exception e) { //TODO: Should we continue on or stop right now? skippedEntries++; int msgID = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED; String message = getMessage(msgID, indexName, stackTraceToSingleLineString(e)); logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.MILD_ERROR, message, msgID); if (debugEnabled()) { debugCaught(DebugLogLevel.ERROR, e); } } } } finally { cursor.close(); } } /** * Rebuild the ID2Subtree internal index. This depends on the DN2ID and DN2URI * databases being complete. * * @throws DatabaseException if an error occurs during rebuild. */ private void rebuildID2Subtree() throws DatabaseException { Index id2subtree = ec.getID2Subtree(); Transaction txn = ec.beginTransaction(); try { //Delete all records in the indexType databases. //TODO: Should we do a transactional delete? id2subtree.clear(txn); } finally { EntryContainer.transactionCommit(txn); } DN2ID dn2id = ec.getDN2ID(); DN2URI dn2uri = ec.getDN2URI(); //Iterate through the id2entry database and insert associated dn2subtree //records. Cursor cursor = id2entry.openCursor(null, null); try { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); LockMode lockMode = LockMode.DEFAULT; OperationStatus status; for (status = cursor.getFirst(key, data, lockMode); status == OperationStatus.SUCCESS; status = cursor.getNext(key, data, lockMode)) { try { EntryID entryID = new EntryID(key); Entry entry = JebFormat.entryFromDatabase(data.getData()); // Check that the parent entry exists. DN parentDN = ec.getParentWithinBase(entry.getDN()); if (parentDN != null) { boolean success = true; // Check for referral entries above the target. dn2uri.targetEntryReferrals(entry.getDN(), null); // Read the parent ID from dn2id. EntryID parentID = dn2id.get(null, parentDN); if (parentID != null) { // Insert into id2subtree for parent ID. if(!id2subtree.insertID(null, parentID.getDatabaseEntry(), entryID)) { success = false; } // Iterate up through the superior entries, starting above the // parent. for (DN dn = ec.getParentWithinBase(parentDN); dn != null; dn = ec.getParentWithinBase(dn)) { // Read the ID from dn2id. EntryID nodeID = dn2id.get(null, dn); if (nodeID != null) { // Insert into id2subtree for this node. if(!id2subtree.insertID(null, nodeID.getDatabaseEntry(), entryID)) { success = false; } } else { int msgID = MSGID_JEB_MISSING_DN2ID_RECORD; String msg = getMessage(msgID, dn.toNormalizedString()); throw new JebException(msgID, msg); } } } else { int msgID = MSGID_JEB_MISSING_DN2ID_RECORD; String msg = getMessage(msgID, parentDN.toNormalizedString()); throw new JebException(msgID, msg); } if(success) { rebuiltEntries++; } else { // The entry alreadly exists in the database. // This could happen if some other process got to this entry // before we did. Since the backend should be offline, this // might be a problem. if(debugEnabled()) { duplicatedEntries++; debugInfo("Unable to insert entry with DN %s and ID %d " + "into the DN2Subtree database because it already " + "exists.", entry.getDN().toString(), entryID.longValue()); } } } else { skippedEntries++; } processedEntries++; } catch (Exception e) { //TODO: Should we continue on or stop right now? skippedEntries++; int msgID = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED; String message = getMessage(msgID, indexName, stackTraceToSingleLineString(e)); logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.MILD_ERROR, message, msgID); if (debugEnabled()) { debugCaught(DebugLogLevel.ERROR, e); } } } } finally { cursor.close(); } } /** * Rebuild the attribute index. * * @param index The indexType to rebuild. * @throws DatabaseException if an error occurs during rebuild. */ private void rebuildAttributeIndex(AttributeIndex index) throws DatabaseException { Transaction txn = ec.beginTransaction(); try { //Delete all records in the indexType databases. //TODO: Should we do a transactional delete? index.clear(txn); } finally { EntryContainer.transactionCommit(txn); } //Iterate through the id2entry database and insert associated indexType //records. Cursor cursor = id2entry.openCursor(null, null); try { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); LockMode lockMode = LockMode.DEFAULT; OperationStatus status; for (status = cursor.getFirst(key, data, lockMode); status == OperationStatus.SUCCESS; status = cursor.getNext(key, data, lockMode)) { try { EntryID entryID = new EntryID(key); Entry entry = JebFormat.entryFromDatabase(data.getData()); // Insert into attribute indexType. if(index.addEntry(null, entryID, entry)) { rebuiltEntries++; } else { // The entry alreadly exists in one or more entry sets. // This could happen if some other process got to this entry // before we did. Since the backend should be offline, this // might be a problem. if(debugEnabled()) { duplicatedEntries++; debugInfo("Unable to insert entry with DN %s and ID %d " + "into the DN2Subtree database because it already " + "exists.", entry.getDN().toString(), entryID.longValue()); } } processedEntries++; } catch (Exception e) { //TODO: Should we continue on or stop right now? skippedEntries++; int msgID = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED; String message = getMessage(msgID, indexName, stackTraceToSingleLineString(e)); logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.MILD_ERROR, message, msgID); if (debugEnabled()) { debugCaught(DebugLogLevel.ERROR, e); } } } } finally { cursor.close(); } } /** * Rebuild the partial attribute index. * * @param index The indexType to rebuild. * @throws DatabaseException if an error occurs during rebuild. */ private void rebuildAttributeIndex(Index index) throws DatabaseException { Transaction txn = ec.beginTransaction(); try { //Delete all records in the indexType databases. //TODO: Should we do a transactional delete? index.clear(txn); } finally { EntryContainer.transactionCommit(txn); } //Iterate through the id2entry database and insert associated indexType //records. Cursor cursor = id2entry.openCursor(null, null); try { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); LockMode lockMode = LockMode.DEFAULT; OperationStatus status; for (status = cursor.getFirst(key, data, lockMode); status == OperationStatus.SUCCESS; status = cursor.getNext(key, data, lockMode)) { try { EntryID entryID = new EntryID(key); Entry entry = JebFormat.entryFromDatabase(data.getData()); // Insert into attribute indexType. if(index.addEntry(null, entryID, entry)) { rebuiltEntries++; } else { // The entry alreadly exists in one or more entry sets. // This could happen if some other process got to this entry // before we did. Since the backend should be offline, this // might be a problem. if(debugEnabled()) { duplicatedEntries++; debugInfo("Unable to insert entry with DN %s and ID %d " + "into the DN2Subtree database because it already " + "exists.", entry.getDN().toString(), entryID.longValue()); } } processedEntries++; } catch (Exception e) { //TODO: Should we continue on or stop right now? skippedEntries++; int msgID = MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED; String message = getMessage(msgID, indexName, stackTraceToSingleLineString(e)); logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.MILD_ERROR, message, msgID); if (debugEnabled()) { debugCaught(DebugLogLevel.ERROR, e); } } } } finally { cursor.close(); } } /** * Get the total entries to process in the rebuild. * * @return The total entries to process. * @throws DatabaseException if an error occurs while getting the total * number of entries to process. */ public long getTotalEntries() throws DatabaseException { //If total entries is not calculated yet, do it now. if(totalEntries < 0) { totalEntries = id2entry.getRecordCount(); } return totalEntries; } /** * Get the number of entries processed in the rebuild. * * @return The total entries processed. */ public long getProcessedEntries() { return processedEntries; } /** * Get the number of entries successfully rebuilt. * * @return The number of entries successfully rebuilt. */ public long getRebuiltEntries() { return rebuiltEntries; } /** * Get the number of entries that encountered duplicated indexType values in * the rebuild process. * * @return The number of entries that encountered duplicated indexType values * in the rebuild process. */ public long getDuplicatedEntries() { return duplicatedEntries; } /** * Get the number of entries skipped because they were either not applicable * or an error occured during the process. * * @return The number of entries skipped. */ public long getSkippedEntries() { return skippedEntries; } /** * Get the index type being rebuilt by this thread. * * @return The index type being rebuilt by this thread. */ public IndexType getIndexType() { return indexType; } }