package eu.fbk.knowledgestore.datastore;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import org.openrdf.model.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.data.Record;
import eu.fbk.knowledgestore.data.Stream;
import eu.fbk.knowledgestore.data.XPath;
/**
* A {@code DataStore} wrapper that log calls to the operations of a wrapped {@code DataStore} and
* their execution times.
* <p>
* This wrapper intercepts calls to an underlying {@code DataStore} and to the
* {@code DataTransaction}s it creates, and logs request information and execution times via SLF4J
* (level DEBUG, logger named after this class). The overhead introduced by this wrapper when
* logging is disabled is negligible.
* </p>
*/
public final class LoggingDataStore extends ForwardingDataStore {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingDataStore.class);
private final DataStore delegate;
/**
* Creates a new instance for the wrapped {@code DataStore} specified.
*
* @param delegate
* the wrapped {@code DataStore}
*/
public LoggingDataStore(final DataStore delegate) {
this.delegate = Preconditions.checkNotNull(delegate);
LOGGER.debug("{} configured", getClass().getSimpleName());
}
@Override
protected DataStore delegate() {
return this.delegate;
}
@Override
public void init() throws IOException {
if (LOGGER.isDebugEnabled()) {
final long ts = System.currentTimeMillis();
super.init();
LOGGER.debug("{} - initialized in {} ms", this, System.currentTimeMillis() - ts);
} else {
super.init();
}
}
@Override
public DataTransaction begin(final boolean readOnly) throws IOException, IllegalStateException {
if (LOGGER.isDebugEnabled()) {
final long ts = System.currentTimeMillis();
final DataTransaction transaction = new LoggingDataTransaction(super.begin(readOnly));
LOGGER.debug("{} - started in {} mode in {} ms", transaction, readOnly ? "read-only"
: "read-write", System.currentTimeMillis() - ts);
return transaction;
} else {
return super.begin(readOnly);
}
}
@Override
public void close() {
if (LOGGER.isDebugEnabled()) {
final long ts = System.currentTimeMillis();
super.close();
LOGGER.debug("{} - closed in {} ms", this, System.currentTimeMillis() - ts);
} else {
super.close();
}
}
private static final class LoggingDataTransaction extends ForwardingDataTransaction {
private final DataTransaction delegate;
LoggingDataTransaction(final DataTransaction delegate) {
this.delegate = Preconditions.checkNotNull(delegate);
}
@Override
protected DataTransaction delegate() {
return this.delegate;
}
@Nullable
private <T> Stream<T> logClose(@Nullable final Stream<T> stream, final String name,
final long ts) {
final AtomicLong count = new AtomicLong(0);
final AtomicBoolean eof = new AtomicBoolean(false);
return stream.track(count, eof).onClose(new Runnable() {
@Override
public void run() {
LOGGER.debug("{} - {} closed after {} ms, {} records, eof={}",
LoggingDataTransaction.this, name, System.currentTimeMillis() - ts,
count, eof);
}
});
}
@Override
public Stream<Record> lookup(final URI type, final Set<? extends URI> ids,
@Nullable final Set<? extends URI> properties) throws IOException,
IllegalArgumentException, IllegalStateException {
if (LOGGER.isDebugEnabled()) {
final String name = "lookup() result stream for "
+ Data.toString(type, Data.getNamespaceMap()) + ", " + ids.size() + " ids"
+ (properties == null ? "" : ", " + properties.size() + " properties");
final long ts = System.currentTimeMillis();
final Stream<Record> result = super.lookup(type, ids, properties);
LOGGER.debug("{} - {} obtained in {} ms", this, name, System.currentTimeMillis()
- ts);
return logClose(result, name, ts);
} else {
return super.lookup(type, ids, properties);
}
}
@Override
public Stream<Record> retrieve(final URI type, @Nullable final XPath condition,
@Nullable final Set<? extends URI> properties) throws IOException,
IllegalArgumentException, IllegalStateException {
if (LOGGER.isDebugEnabled()) {
final String name = "retrieve() result stream for "
+ Data.toString(type, Data.getNamespaceMap())
+ (condition == null ? "" : ", " + condition.toString())
+ (properties == null ? "" : ", " + properties.size() + " properties");
final long ts = System.currentTimeMillis();
final Stream<Record> result = super.retrieve(type, condition, properties);
LOGGER.debug("{} - {} obtained in {} ms", this, name, System.currentTimeMillis()
- ts);
return logClose(result, name, ts);
} else {
return super.retrieve(type, condition, properties);
}
}
@Override
public long count(final URI type, @Nullable final XPath condition) throws IOException,
IllegalArgumentException, IllegalStateException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{} - counting {}{}", this,
Data.toString(type, Data.getNamespaceMap()), //
condition == null ? "" : ", " + condition.toString());
final long ts = System.currentTimeMillis();
final long result = super.count(type, condition);
LOGGER.debug("{} - count result {} obtained in {} ms", this, result,
System.currentTimeMillis() - ts);
return result;
} else {
return super.count(type, condition);
}
}
@Override
public Stream<Record> match(final Map<URI, XPath> conditions,
final Map<URI, Set<URI>> ids, final Map<URI, Set<URI>> properties)
throws IOException, IllegalStateException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{} - evaluating match: conditions={}, ids={}, properties={}", this,
conditions, ids, properties);
final String name = "match() result stream";
final long ts = System.currentTimeMillis();
final Stream<Record> result = super.match(conditions, ids, properties);
LOGGER.debug("{} - {} obtained in {} ms", this, name, System.currentTimeMillis()
- ts);
return logClose(result, name, ts);
} else {
return super.match(conditions, ids, properties);
}
}
@Override
public void store(final URI type, final Record record) throws IOException,
IllegalStateException {
if (LOGGER.isDebugEnabled()) {
final long ts = System.currentTimeMillis();
super.store(type, record);
LOGGER.debug("{} - {} {} stored in {} ms", this,
Data.toString(type, Data.getNamespaceMap()), record,
System.currentTimeMillis() - ts);
} else {
super.store(type, record);
}
}
@Override
public void delete(final URI type, final URI id) throws IOException, IllegalStateException {
if (LOGGER.isDebugEnabled()) {
final long ts = System.currentTimeMillis();
super.delete(type, id);
LOGGER.debug("{} - {} {} deleted in {} ms", this,
Data.toString(type, Data.getNamespaceMap()), id,
System.currentTimeMillis() - ts);
} else {
super.delete(type, id);
}
}
@Override
public void end(final boolean commit) throws IOException, IllegalStateException {
if (LOGGER.isDebugEnabled()) {
final long ts = System.currentTimeMillis();
super.end(commit);
LOGGER.debug("{} - {} done in {} ms", this, commit ? "commit" : "rollback",
System.currentTimeMillis() - ts);
} else {
super.end(commit);
}
}
}
}