package org.sdnplatform.sync.internal;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.sql.ConnectionPoolDataSource;
import net.floodlightcontroller.core.annotations.LogMessageDoc;
import org.sdnplatform.sync.Versioned;
import org.sdnplatform.sync.ISyncService.Scope;
import org.sdnplatform.sync.error.PersistException;
import org.sdnplatform.sync.error.SyncException;
import org.sdnplatform.sync.internal.store.IStorageEngine;
import org.sdnplatform.sync.internal.store.InMemoryStorageEngine;
import org.sdnplatform.sync.internal.store.JavaDBStorageEngine;
import org.sdnplatform.sync.internal.store.SynchronizingStorageEngine;
import org.sdnplatform.sync.internal.util.ByteArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manage registered stores and associated metadata
* @author readams
*/
public class StoreRegistry {
protected static final Logger logger =
LoggerFactory.getLogger(StoreRegistry.class);
/**
* The associated syncManager
*/
private final SyncManager syncManager;
/**
* Directory where the persistent store will be located
*/
private final String dbPath;
/**
* A data source suitable for use in persistent stores
*/
private ConnectionPoolDataSource persistentDataSource;
/**
* The storage engines that contain the locally-stored data
*/
private HashMap<String,SynchronizingStorageEngine> localStores =
new HashMap<String, SynchronizingStorageEngine>();
/**
* Undelivered hints associated with the stores
*/
private InMemoryStorageEngine<HintKey,byte[]> hints;
/**
* A queue containing pending hints.
*/
private ArrayDeque<HintKey> hintQueue = new ArrayDeque<HintKey>();
private Lock hintLock = new ReentrantLock();
private Condition hintsAvailable = hintLock.newCondition();
/**
* Construct a new {@link StoreRegistry}
* @param syncManager The associated syncManager
*/
public StoreRegistry(SyncManager syncManager, String dbPath) {
super();
this.syncManager = syncManager;
this.dbPath = dbPath;
hints = new InMemoryStorageEngine<HintKey, byte[]>("system-hints");
}
// **************
// public methods
// **************
/**
* Get the store associated with the given name, or null if there is no
* such store
* @param storeName
* @return a {@link SynchronizingStorageEngine}
*/
public SynchronizingStorageEngine get(String storeName) {
return localStores.get(storeName);
}
/**
* Register a new store with the given name, scope and persistence
* @param storeName the name of the store
* @param scope the scope for the store
* @param persistent whether the store should be persistent
* @return the newly-allocated store
* @throws PersistException
*/
public synchronized SynchronizingStorageEngine register(String storeName,
Scope scope,
boolean persistent)
throws PersistException {
SynchronizingStorageEngine store =
localStores.get(storeName);
if (store != null) {
return store;
}
IStorageEngine<ByteArray, byte[]> dstore;
if (persistent) {
if (persistentDataSource == null)
persistentDataSource = JavaDBStorageEngine.getDataSource(dbPath, false);
dstore = new JavaDBStorageEngine(storeName, persistentDataSource);
} else {
dstore = new InMemoryStorageEngine<ByteArray, byte[]>(storeName);
}
store = new SynchronizingStorageEngine(dstore, syncManager,
syncManager.debugCounter,
scope);
localStores.put(storeName, store);
return store;
}
/**
* Get a collection containing all the currently-registered stores
* @return the {@link Collection<SynchronizingStorageEngine>}
*/
public Collection<SynchronizingStorageEngine> values() {
return localStores.values();
}
/**
* Add a key/value to the hint store for the given store
* @param storeName the name of the store for the keyed value
* @param key the key
* @param value the value
*/
@LogMessageDoc(level="ERROR",
message="Failed to queue hint for store {storeName}",
explanation="There was an error synchronizing data to " +
"remote nodes",
recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
public void queueHint(String storeName,
ByteArray key, Versioned<byte[]> value) {
try {
HintKey hk = new HintKey(storeName,key);
hintLock.lock();
try {
boolean needed = !hints.containsKey(hk);
needed &= hints.doput(hk, value);
if (needed) {
hintQueue.add(hk);
hintsAvailable.signal();
}
} finally {
hintLock.unlock();
}
} catch (SyncException e) {
logger.error("Failed to queue hint for store " + storeName, e);
}
}
/**
* Drain up to the given number of hints to the provided collection.
* This method will block until at least one hint is available
* @param c the collection to which the hints should be copied
* @param maxElements the maximum number of hints to drain
* @throws InterruptedException
*/
public void takeHints(Collection<Hint> c, int maxElements)
throws InterruptedException {
int count = 0;
try {
while (count == 0) {
hintLock.lock();
while (hintQueue.isEmpty()) {
hintsAvailable.await();
}
while (count < maxElements && !hintQueue.isEmpty()) {
HintKey hintKey = hintQueue.pollFirst();
if (hintKey != null) {
List<Versioned<byte[]>> values = hints.remove(hintKey);
if (values == null) {
continue;
}
c.add(new Hint(hintKey, values));
count += 1;
}
}
}
} finally {
hintLock.unlock();
}
}
public void shutdown() {
hintQueue.clear();
hints.close();
}
/**
* A key in the hint store
* @author readams
*/
public static class HintKey {
private final String storeName;
private final ByteArray key;
private final short nodeId;
public HintKey(String storeName,
ByteArray key,
short nodeId) {
super();
this.storeName = storeName;
this.key = key;
this.nodeId = nodeId;
}
public HintKey(String storeName,
ByteArray key) {
super();
this.storeName = storeName;
this.key = key;
this.nodeId = -1;
}
public String getStoreName() {
return storeName;
}
public ByteArray getKey() {
return key;
}
public short getNodeId() {
return nodeId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + nodeId;
result = prime * result
+ ((storeName == null) ? 0 : storeName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
HintKey other = (HintKey) obj;
if (key == null) {
if (other.key != null) return false;
} else if (!key.equals(other.key)) return false;
if (nodeId != other.nodeId) return false;
if (storeName == null) {
if (other.storeName != null) return false;
} else if (!storeName.equals(other.storeName)) return false;
return true;
}
}
/**
* A hint representing a hint key and a value
* @author readams
*/
public static class Hint {
private HintKey hintKey;
private List<Versioned<byte[]>> values;
public Hint(HintKey hintKey, List<Versioned<byte[]>> values) {
super();
this.hintKey = hintKey;
this.values = values;
}
public HintKey getHintKey() {
return hintKey;
}
public List<Versioned<byte[]>> getValues() {
return values;
}
}
}