| | |
| | | |
| | | import java.io.Closeable; |
| | | |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.ByteSequence; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.opends.server.backends.pluggable.spi.Cursor; |
| | | import org.opends.server.backends.pluggable.spi.ReadableStorage; |
| | | import org.opends.server.backends.pluggable.spi.Storage; |
| | | import org.opends.server.backends.pluggable.spi.StorageRuntimeException; |
| | | import org.opends.server.backends.pluggable.spi.TreeName; |
| | | import org.opends.server.backends.pluggable.spi.WriteableStorage; |
| | | import org.opends.server.util.ServerConstants; |
| | | import org.opends.server.util.StaticUtils; |
| | | |
| | | /** |
| | | * This class is a wrapper around the JE database object and provides basic |
| | |
| | | */ |
| | | abstract class DatabaseContainer implements Closeable |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** The database entryContainer. */ |
| | | final EntryContainer entryContainer; |
| | | |
| | | /** The name of the database within the entryContainer. */ |
| | | TreeName treeName; |
| | | private TreeName name; |
| | | |
| | | /** The reference to the JE Storage. */ |
| | | final Storage storage; |
| | |
| | | { |
| | | this.storage = storage; |
| | | this.entryContainer = entryContainer; |
| | | this.treeName = treeName; |
| | | this.name = treeName; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | void open(WriteableStorage txn) throws StorageRuntimeException |
| | | { |
| | | txn.openTree(treeName); |
| | | if (logger.isTraceEnabled()) |
| | | { |
| | | logger.trace("JE database %s opened. txn=%s", treeName, txn); |
| | | } |
| | | // FIXME: remove? |
| | | txn.openTree(name); |
| | | } |
| | | |
| | | /** |
| | |
| | | @Override |
| | | public synchronized void close() throws StorageRuntimeException |
| | | { |
| | | storage.closeTree(treeName); |
| | | |
| | | if(logger.isTraceEnabled()) |
| | | { |
| | | logger.trace("Closed tree %s", treeName); |
| | | } |
| | | } |
| | | |
| | | void put(WriteableStorage txn, ByteSequence key, ByteSequence value) throws StorageRuntimeException |
| | | { |
| | | txn.create(treeName, key, value); |
| | | if (logger.isTraceEnabled()) |
| | | { |
| | | logger.trace(messageToLog(true, treeName, txn, key, value)); |
| | | } |
| | | } |
| | | |
| | | ByteString read(ReadableStorage txn, ByteSequence key, boolean isRMW) throws StorageRuntimeException |
| | | { |
| | | ByteString value = isRMW ? txn.getRMW(treeName, key) : txn.read(treeName, key); |
| | | if (logger.isTraceEnabled()) |
| | | { |
| | | logger.trace(messageToLog(value != null, treeName, txn, key, value)); |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | /** |
| | | * Insert a record into a JE database, with optional debug logging. This is a |
| | | * simple wrapper around the JE Database.putNoOverwrite method. |
| | | * @param txn The JE transaction handle, or null if none. |
| | | * @param key The record key. |
| | | * @param value The record value. |
| | | * @return {@code true} if the key-value mapping could be inserted, |
| | | * {@code false} if the key was already mapped to another value |
| | | * @throws StorageRuntimeException If an error occurs in the JE operation. |
| | | */ |
| | | boolean insert(WriteableStorage txn, ByteString key, ByteString value) throws StorageRuntimeException |
| | | { |
| | | boolean result = txn.putIfAbsent(treeName, key, value); |
| | | if (logger.isTraceEnabled()) |
| | | { |
| | | logger.trace(messageToLog(result, treeName, txn, key, value)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Delete a record from a JE database, with optional debug logging. This is a |
| | | * simple wrapper around the JE Database.delete method. |
| | | * @param txn The JE transaction handle, or null if none. |
| | | * @param key The key of the record to be read. |
| | | * @return {@code true} if the key mapping was removed, {@code false} otherwise |
| | | * @throws StorageRuntimeException If an error occurs in the JE operation. |
| | | */ |
| | | boolean delete(WriteableStorage txn, ByteSequence key) throws StorageRuntimeException |
| | | { |
| | | final boolean result = txn.delete(treeName, key); |
| | | if (logger.isTraceEnabled()) |
| | | { |
| | | logger.trace(messageToLog(result, treeName, txn, key, null)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Open a JE cursor on the JE database. This is a simple wrapper around |
| | | * the JE Database.openCursor method. |
| | | * @param txn A JE database transaction to be used by the cursor, |
| | | * or null if none. |
| | | * @return A JE cursor. |
| | | * @throws StorageRuntimeException If an error occurs while attempting to open |
| | | * the cursor. |
| | | */ |
| | | final Cursor openCursor(ReadableStorage txn) throws StorageRuntimeException |
| | | { |
| | | return txn.openCursor(treeName); |
| | | // FIXME: is this method needed? |
| | | storage.closeTree(name); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | long getRecordCount(ReadableStorage txn) throws StorageRuntimeException |
| | | { |
| | | long count = count(txn); |
| | | if (logger.isTraceEnabled()) |
| | | { |
| | | logger.trace(messageToLog(true, treeName, txn, null, null)); |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private long count(ReadableStorage txn) |
| | | { |
| | | final Cursor cursor = txn.openCursor(treeName); |
| | | /* |
| | | * FIXME: push down to storage. Some DBs have native support for this, e.g. using counted |
| | | * B-Trees. |
| | | */ |
| | | final Cursor cursor = txn.openCursor(name); |
| | | try |
| | | { |
| | | long count = 0; |
| | |
| | | @Override |
| | | public String toString() |
| | | { |
| | | return treeName.toString(); |
| | | return name.toString(); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | final TreeName getName() |
| | | { |
| | | return treeName; |
| | | return name; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | final void setName(TreeName name) |
| | | { |
| | | this.treeName = name; |
| | | this.name = name; |
| | | } |
| | | |
| | | /** Returns the message to log given the provided information. */ |
| | | private String messageToLog(boolean success, TreeName treeName, ReadableStorage txn, ByteSequence key, |
| | | ByteSequence value) |
| | | { |
| | | StringBuilder builder = new StringBuilder(); |
| | | builder.append(" ("); |
| | | builder.append(success ? "SUCCESS" : "ERROR"); |
| | | builder.append(")"); |
| | | builder.append(" db="); |
| | | builder.append(treeName); |
| | | builder.append(" txn="); |
| | | builder.append(txn); |
| | | builder.append(ServerConstants.EOL); |
| | | if (key != null) |
| | | { |
| | | builder.append("key:"); |
| | | builder.append(ServerConstants.EOL); |
| | | StaticUtils.byteArrayToHexPlusAscii(builder, key.toByteArray(), 4); |
| | | } |
| | | |
| | | // If the operation was successful we log the same common information |
| | | // plus the data |
| | | if (value != null) |
| | | { |
| | | builder.append("value(len="); |
| | | builder.append(value.length()); |
| | | builder.append("):"); |
| | | builder.append(ServerConstants.EOL); |
| | | StaticUtils.byteArrayToHexPlusAscii(builder, value.toByteArray(), 4); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | } |