/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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
*
*
* Copyright 2015 ForgeRock AS
*/
package org.opends.server.backends.pluggable;
import java.util.NoSuchElementException;
import java.util.Set;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.backends.pluggable.spi.AccessMode;
import org.opends.server.backends.pluggable.spi.Cursor;
import org.opends.server.backends.pluggable.spi.Importer;
import org.opends.server.backends.pluggable.spi.ReadOperation;
import org.opends.server.backends.pluggable.spi.ReadableTransaction;
import org.opends.server.backends.pluggable.spi.SequentialCursor;
import org.opends.server.backends.pluggable.spi.Storage;
import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
import org.opends.server.backends.pluggable.spi.StorageStatus;
import org.opends.server.backends.pluggable.spi.TreeName;
import org.opends.server.backends.pluggable.spi.UpdateFunction;
import org.opends.server.backends.pluggable.spi.WriteOperation;
import org.opends.server.backends.pluggable.spi.WriteableTransaction;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.RestoreConfig;
/** Decorates a {@link Storage} with additional trace logging. */
final class TracedStorage implements Storage
{
private void appendKeyValue(final StringBuilder builder, final Object key, final Object value)
{
builder.append("\"").append(key).append("\":\"").append(value).append("\"");
}
/**
* Decorates {@link Cursor} with trace logging. Navigational methods will perform enter/leave logging in order to
* help detect deadlocks.
*/
private class TracedCursor implements Cursor<ByteString, ByteString>
{
private final Cursor<ByteString, ByteString> cursor;
private TracedCursor(final Cursor<ByteString, ByteString> cursor)
{
this.cursor = cursor;
}
private void traceEnter(String method, Object... args)
{
trace("Cursor", id(), method + "#enter", args);
}
private void traceLeave(String method, Object... args)
{
trace("Cursor", id(), method + "#leave", args);
}
@Override
public boolean positionToKey(final ByteSequence key)
{
traceEnter("positionToKey", "key", hex(key));
boolean found = cursor.positionToKey(key);
traceLeave("positionToKey", "key", hex(key), "found", found);
return found;
}
@Override
public boolean positionToKeyOrNext(final ByteSequence key)
{
traceEnter("positionToKeyOrNext", "key", hex(key));
boolean found = cursor.positionToKeyOrNext(key);
traceLeave("positionToKeyOrNext", "key", hex(key), "found", found);
return found;
}
@Override
public boolean positionToLastKey()
{
traceEnter("positionToLastKey");
boolean found = cursor.positionToLastKey();
traceLeave("positionToLastKey", "found", found);
return found;
}
@Override
public boolean positionToIndex(final int index)
{
traceEnter("positionToIndex", "index", index);
boolean found = cursor.positionToIndex(index);
traceLeave("positionToIndex", "index", index, "found", found);
return found;
}
@Override
public boolean next()
{
traceEnter("next");
boolean found = cursor.next();
traceLeave("next", "found", found);
return found;
}
@Override
public void delete()
{
traceEnter("delete");
cursor.delete();
traceLeave("delete");
}
@Override
public boolean isDefined()
{
traceEnter("isDefined");
boolean isDefined = cursor.isDefined();
traceLeave("isDefined", "isDefined", isDefined);
return isDefined;
}
@Override
public ByteString getKey() throws NoSuchElementException
{
traceEnter("getKey");
try
{
ByteString key = cursor.getKey();
traceLeave("getKey", "key", hex(key));
return key;
}
catch (NoSuchElementException e)
{
traceLeave("getKey", "NoSuchElementException", e.getMessage());
throw e;
}
}
@Override
public ByteString getValue() throws NoSuchElementException
{
traceEnter("getValue");
try
{
ByteString value = cursor.getValue();
traceLeave("getValue", "value", hex(value));
return value;
}
catch (NoSuchElementException e)
{
traceLeave("getValue", "NoSuchElementException", e.getMessage());
throw e;
}
}
@Override
public void close()
{
traceEnter("close");
cursor.close();
traceLeave("close");
}
private int id()
{
return System.identityHashCode(this);
}
}
/** Decorates an {@link Importer} with additional trace logging. */
private final class TracedImporter implements Importer
{
private final Importer importer;
private TracedImporter(final Importer importer)
{
this.importer = importer;
}
private void traceEnter(String method, Object... args)
{
trace("Importer", id(), method + "#enter", args);
}
private void traceLeave(String method, Object... args)
{
trace("Importer", id(), method + "#leave", args);
}
@Override
public void clearTree(final TreeName name)
{
traceEnter("clearTree", "name", name);
importer.clearTree(name);
traceLeave("clearTree", "name", name);
}
@Override
public void put(final TreeName name, final ByteSequence key, final ByteSequence value)
{
traceEnter("put", "name", name, "key", hex(key), "value", hex(value));
importer.put(name, key, value);
traceLeave("put", "name", name, "key", hex(key), "value", hex(value));
}
@Override
public ByteString read(TreeName name, ByteSequence key)
{
traceEnter("read", "name", name, "key", hex(key));
final ByteString value = importer.read(name, key);
traceLeave("read", "name", name, "key", hex(key), "value", hex(value));
return value;
}
@Override
public void close()
{
traceEnter("close");
importer.close();
traceLeave("close");
}
private int id()
{
return System.identityHashCode(this);
}
@Override
public SequentialCursor<ByteString, ByteString> openCursor(TreeName name)
{
traceEnter("openCursor", "name", name);
SequentialCursor<ByteString, ByteString> cursor = importer.openCursor(name);
traceLeave("openCursor", "name", name);
return cursor;
}
}
/** Decorates an {@link ReadableTransaction} with additional trace logging. */
private final class TracedReadableTransaction implements ReadableTransaction
{
private final ReadableTransaction txn;
private TracedReadableTransaction(final ReadableTransaction txn)
{
this.txn = txn;
}
private void traceEnter(String method, Object... args)
{
trace("ReadableTransaction", id(), method + "#enter", args);
}
private void traceLeave(String method, Object... args)
{
trace("ReadableTransaction", id(), method + "#leave", args);
}
@Override
public long getRecordCount(TreeName name)
{
traceEnter("getRecordCount", "name", name);
final long count = txn.getRecordCount(name);
traceLeave("getRecordCount", "name", name, "count", count);
return count;
}
@Override
public Cursor<ByteString, ByteString> openCursor(final TreeName name)
{
traceEnter("openCursor", "name", name);
final Cursor<ByteString, ByteString> cursor = txn.openCursor(name);
traceLeave("openCursor", "name", name);
return new TracedCursor(cursor);
}
@Override
public ByteString read(final TreeName name, final ByteSequence key)
{
traceEnter("read", "name", name, "key", hex(key));
final ByteString value = txn.read(name, key);
traceLeave("read", "name", name, "key", hex(key), "value", hex(value));
return value;
}
private int id()
{
return System.identityHashCode(this);
}
}
/** Decorates an {@link WriteableTransaction} with additional trace logging. */
private final class TracedWriteableTransaction implements WriteableTransaction
{
private final WriteableTransaction txn;
private TracedWriteableTransaction(final WriteableTransaction txn)
{
this.txn = txn;
}
private void traceEnter(String method, Object... args)
{
trace("WriteableTransaction", id(), method + "#enter", args);
}
private void traceLeave(String method, Object... args)
{
trace("WriteableTransaction", id(), method + "#leave", args);
}
@Override
public void put(final TreeName name, final ByteSequence key, final ByteSequence value)
{
traceEnter("put", "name", name, "key", hex(key), "value", hex(value));
txn.put(name, key, value);
traceLeave("put", "name", name, "key", hex(key), "value", hex(value));
}
@Override
public boolean delete(final TreeName name, final ByteSequence key)
{
traceEnter("delete", "name", name, "key", hex(key));
final boolean isDeleted = txn.delete(name, key);
traceLeave("delete", "name", name, "key", hex(key), "isDeleted", isDeleted);
return isDeleted;
}
@Override
public void deleteTree(final TreeName name)
{
traceEnter("deleteTree", "name", name);
txn.deleteTree(name);
traceLeave("deleteTree", "name", name);
}
@Override
public long getRecordCount(TreeName name)
{
traceEnter("getRecordCount", "name", name);
final long count = txn.getRecordCount(name);
traceLeave("getRecordCount", "name", name, "count", count);
return count;
}
@Override
public Cursor<ByteString, ByteString> openCursor(final TreeName name)
{
traceEnter("openCursor", "name", name);
final Cursor<ByteString, ByteString> cursor = txn.openCursor(name);
traceLeave("openCursor", "name", name);
return new TracedCursor(cursor);
}
@Override
public void openTree(final TreeName name, boolean createOnDemand)
{
traceEnter("openTree", "name", name, "createOnDemand", createOnDemand);
txn.openTree(name, createOnDemand);
traceLeave("openTree", "name", name, "createOnDemand", createOnDemand);
}
@Override
public ByteString read(final TreeName name, final ByteSequence key)
{
traceEnter("read", "name", name, "key", hex(key));
final ByteString value = txn.read(name, key);
traceLeave("read", "name", name, "key", hex(key), "value", hex(value));
return value;
}
@Override
public boolean update(final TreeName name, final ByteSequence key, final UpdateFunction f)
{
traceEnter("update", "name", name, "key", hex(key));
final boolean isUpdated = txn.update(name, key, new UpdateFunction() {
@Override
public ByteSequence computeNewValue(final ByteSequence oldValue) {
traceEnter("updateFunction", "oldValue", hex(oldValue));
ByteSequence newValue = f.computeNewValue(oldValue);
traceLeave("updateFunction", "oldValue", hex(oldValue), "newValue", hex(newValue));
return newValue;
}
});
traceLeave("update", "name", name, "key", hex(key), "isUpdated", isUpdated);
return isUpdated;
}
private int id()
{
return System.identityHashCode(this);
}
}
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private final String backendId;
private final Storage storage;
TracedStorage(final Storage storage, final String backendId)
{
this.storage = storage;
this.backendId = backendId;
}
private void trace(String type, int id, String method, Object... args)
{
StringBuilder builder = new StringBuilder();
// Include a "!" in order to facilitate filtering using commands like "cut".
builder.append("!{");
appendKeyValue(builder, "backend", backendId);
builder.append(",");
appendKeyValue(builder, "storage", storageId());
builder.append(",");
appendKeyValue(builder, "type", type);
builder.append(",");
appendKeyValue(builder, "id", id);
builder.append(",");
appendKeyValue(builder, "method", method);
for (int i = 0; i < args.length; i += 2)
{
builder.append(",");
appendKeyValue(builder, args[i], args[i + 1]);
}
builder.append("}");
logger.trace(builder.toString());
}
private void traceEnter(String method, Object... args)
{
if (logger.isTraceEnabled())
{
trace("Storage", storageId(), method + "#enter", args);
}
}
private void traceLeave(String method, Object... args)
{
if (logger.isTraceEnabled())
{
trace("Storage", storageId(), method + "#leave", args);
}
}
@Override
public void close()
{
traceEnter("close");
storage.close();
traceLeave("close");
}
@Override
public StorageStatus getStorageStatus()
{
return storage.getStorageStatus();
}
@Override
public void open(AccessMode accessMode) throws Exception
{
traceEnter("open", "accessMode", accessMode);
storage.open(accessMode);
traceLeave("open", "accessMode", accessMode);
}
@Override
public <T> T read(final ReadOperation<T> readOperation) throws Exception
{
ReadOperation<T> op = readOperation;
if (logger.isTraceEnabled())
{
op = new ReadOperation<T>()
{
@Override
public T run(final ReadableTransaction txn) throws Exception
{
return readOperation.run(new TracedReadableTransaction(txn));
}
};
}
return storage.read(op);
}
@Override
public void removeStorageFiles() throws StorageRuntimeException
{
traceEnter("removeStorageFiles");
storage.removeStorageFiles();
traceLeave("removeStorageFiles");
}
@Override
public Importer startImport() throws ConfigException, StorageRuntimeException
{
traceEnter("startImport");
final Importer importer = storage.startImport();
traceLeave("startImport");
return logger.isTraceEnabled() ? new TracedImporter(importer) : importer;
}
@Override
public boolean supportsBackupAndRestore()
{
return storage.supportsBackupAndRestore();
}
@Override
public void write(final WriteOperation writeOperation) throws Exception
{
WriteOperation op = writeOperation;
if (logger.isTraceEnabled())
{
op = new WriteOperation()
{
@Override
public void run(final WriteableTransaction txn) throws Exception
{
writeOperation.run(new TracedWriteableTransaction(txn));
}
};
}
storage.write(op);
}
@Override
public void createBackup(BackupConfig backupConfig) throws DirectoryException
{
traceEnter("createBackup");
storage.createBackup(backupConfig);
traceLeave("createBackup");
}
@Override
public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
{
traceEnter("removeBackup", "backupID", backupID);
storage.removeBackup(backupDirectory, backupID);
traceLeave("removeBackup", "backupID", backupID);
}
@Override
public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
{
traceEnter("restoreBackup");
storage.restoreBackup(restoreConfig);
traceLeave("restoreBackup");
}
@Override
public Set<TreeName> listTrees()
{
traceEnter("listTrees");
final Set<TreeName> results = storage.listTrees();
traceLeave("listTrees", "trees", results);
return results;
}
private static String hex(final ByteSequence bytes)
{
return bytes != null ? bytes.toByteString().toASCIIString() : null;
}
private int storageId()
{
return System.identityHashCode(this);
}
}