/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.cache;
import java.util.Collection;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.engine.cache.AbstractBerkeleyDBWorker.PoisonRequest;
import com.opengamma.util.ArgumentChecker;
import com.sleepycat.bind.tuple.LongBinding;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
/**
* An implementation of {@link BinaryDataStore} which backs all data on a BerkeleyDB table.
*/
public class BerkeleyDBBinaryDataStore extends AbstractBerkeleyDBComponent implements BinaryDataStore {
private static final Logger s_logger = LoggerFactory.getLogger(BerkeleyDBBinaryDataStore.class);
private final class Worker extends AbstractBerkeleyDBWorker implements BinaryDataStore {
private final DatabaseEntry _key = new DatabaseEntry();
private final DatabaseEntry _value = new DatabaseEntry();
public Worker(final BlockingQueue<Request> requests) {
super(null, requests);
}
@Override
public void delete() {
getDatabase().close();
// TODO kirk 2010-08-07 -- For batch operation, we'd have to explicitly remove the DB as well.
// getDbEnvironment().removeDatabase(null, getDatabaseName());
stop();
}
@Override
public byte[] get(final long identifier) {
LongBinding.longToEntry(identifier, _key);
OperationStatus opStatus = getDatabase().get(getTransaction(), _key, _value, LockMode.READ_UNCOMMITTED);
switch (opStatus) {
case SUCCESS:
return _value.getData();
default:
s_logger.debug("{} - No record available for identifier {} status {}", new Object[] {getDatabaseName(), identifier, opStatus });
return null;
}
}
@Override
public void put(final long identifier, final byte[] data) {
LongBinding.longToEntry(identifier, _key);
_value.setData(data);
OperationStatus opStatus = getDatabase().put(getTransaction(), _key, _value);
switch (opStatus) {
case SUCCESS:
return;
default:
s_logger.warn("{} - Unable to write to identifier {} status {}", new Object[] {getDatabaseName(), identifier, opStatus });
}
}
@Override
public Map<Long, byte[]> get(final Collection<Long> identifiers) {
return AbstractBinaryDataStore.get(this, identifiers);
}
@Override
public void put(final Map<Long, byte[]> data) {
AbstractBinaryDataStore.put(this, data);
}
}
private BlockingQueue<Worker.Request> _requests;
public BerkeleyDBBinaryDataStore(Environment dbEnvironment, String databaseName) {
super(dbEnvironment, databaseName);
}
@Override
public void start() {
synchronized (this) {
if (_requests == null) {
_requests = new LinkedBlockingQueue<Worker.Request>();
// TODO: We can have multiple worker threads -- will that be good or bad?
final Thread worker = new Thread(new Worker(_requests));
worker.setName("BerkeleyDBBinaryDataStore-Worker");
worker.setDaemon(true);
worker.start();
}
}
super.start();
}
@Override
public void stop() {
synchronized (this) {
if (_requests != null) {
_requests.add(new PoisonRequest());
_requests = null;
}
}
}
@Override
protected DatabaseConfig getDatabaseConfig() {
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
dbConfig.setTransactional(false);
// TODO kirk 2010-08-07 -- For Batch operation, this should be set to false probably.
dbConfig.setTemporary(true);
// TODO kirk 2010-08-07 -- For Batch operation, this should be set to true probably.
dbConfig.setDeferredWrite(false);
return dbConfig;
}
private static final class DeleteRequest extends Worker.Request {
@Override
protected void runInTransaction(final AbstractBerkeleyDBWorker worker) {
((Worker) worker).delete();
}
public void run(final Queue<Worker.Request> requests) {
requests.add(this);
waitFor();
}
}
@Override
public void delete() {
new DeleteRequest().run(_requests);
}
private static final class GetRequest extends Worker.Request {
private final long _identifier;
private byte[] _result;
public GetRequest(final long identifier) {
_identifier = identifier;
}
@Override
protected void runInTransaction(final AbstractBerkeleyDBWorker worker) {
_result = ((Worker) worker).get(_identifier);
}
public byte[] run(final Queue<Worker.Request> requests) {
requests.add(this);
waitFor();
return _result;
}
}
@Override
public byte[] get(long identifier) {
if (!isRunning()) {
s_logger.info("Starting on first call as wasn't called as part of lifecycle interface");
start();
}
return new GetRequest(identifier).run(_requests);
}
private static final class PutRequest extends Worker.Request {
private final long _identifier;
private final byte[] _data;
public PutRequest(final long identifier, final byte[] data) {
_identifier = identifier;
_data = data;
}
@Override
protected void runInTransaction(final AbstractBerkeleyDBWorker worker) {
((Worker) worker).put(_identifier, _data);
}
public void run(final Queue<Worker.Request> requests) {
requests.add(this);
waitFor();
}
}
@Override
public void put(long identifier, byte[] data) {
if (!isRunning()) {
s_logger.info("Starting on first call as wasn't called as part of lifecycle interface");
start();
}
ArgumentChecker.notNull(data, "data to store");
new PutRequest(identifier, data).run(_requests);
}
private static final class BulkGetRequest extends Worker.Request {
private final Collection<Long> _identifiers;
private Map<Long, byte[]> _result;
public BulkGetRequest(final Collection<Long> identifiers) {
_identifiers = identifiers;
}
@Override
protected void runInTransaction(final AbstractBerkeleyDBWorker worker) {
_result = ((Worker) worker).get(_identifiers);
}
public Map<Long, byte[]> run(final Queue<Worker.Request> requests) {
requests.add(this);
waitFor();
return _result;
}
}
@Override
public Map<Long, byte[]> get(final Collection<Long> identifiers) {
if (!isRunning()) {
s_logger.info("Starting on first call as wasn't called as part of lifecycle interface");
start();
}
return new BulkGetRequest(identifiers).run(_requests);
}
private static final class BulkPutRequest extends Worker.Request {
private final Map<Long, byte[]> _data;
public BulkPutRequest(final Map<Long, byte[]> data) {
_data = data;
}
@Override
protected void runInTransaction(final AbstractBerkeleyDBWorker worker) {
((Worker) worker).put(_data);
}
public void run(final Queue<Worker.Request> requests) {
requests.add(this);
waitFor();
}
}
@Override
public void put(final Map<Long, byte[]> data) {
if (!isRunning()) {
s_logger.info("Starting on first call as wasn't called as part of lifecycle interface");
start();
}
new BulkPutRequest(data).run(_requests);
}
}