/*
* Copyright 2008-2009 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package voldemort.store.memory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.log4j.Logger;
import voldemort.VoldemortException;
import voldemort.annotations.concurrency.NotThreadsafe;
import voldemort.store.AbstractStorageEngine;
import voldemort.store.StoreUtils;
import voldemort.utils.ClosableIterator;
import voldemort.utils.Pair;
import voldemort.utils.Utils;
import voldemort.versioning.ObsoleteVersionException;
import voldemort.versioning.Occurred;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;
/**
* A simple non-persistent, in-memory store. Useful for unit testing.
*
* TODO Rewrite this class using striped locks for more granular locking.
*
*/
public class InMemoryStorageEngine<K, V, T> extends AbstractStorageEngine<K, V, T> {
private static final Logger logger = Logger.getLogger(InMemoryStorageEngine.class);
protected final ConcurrentMap<K, List<Versioned<V>>> map;
public InMemoryStorageEngine(String name) {
super(name);
this.map = new ConcurrentHashMap<K, List<Versioned<V>>>();
}
public InMemoryStorageEngine(String name, ConcurrentMap<K, List<Versioned<V>>> map) {
super(name);
this.map = Utils.notNull(map);
}
public synchronized void deleteAll() {
this.map.clear();
}
public boolean delete(K key) {
return delete(key, null);
}
@Override
public synchronized boolean delete(K key, Version version) {
StoreUtils.assertValidKey(key);
List<Versioned<V>> values = map.get(key);
if(values == null) {
return false;
}
if(version == null) {
map.remove(key);
return true;
}
boolean deletedSomething = false;
Iterator<Versioned<V>> iterator = values.iterator();
while(iterator.hasNext()) {
Versioned<V> item = iterator.next();
if(item.getVersion().compare(version) == Occurred.BEFORE) {
iterator.remove();
deletedSomething = true;
}
}
if(values.size() == 0) {
// if there are no more versions left, also remove the key from the
// map
map.remove(key);
}
return deletedSomething;
}
@Override
public List<Version> getVersions(K key) {
return StoreUtils.getVersions(get(key, null));
}
@Override
public synchronized List<Versioned<V>> get(K key, T transform) throws VoldemortException {
StoreUtils.assertValidKey(key);
List<Versioned<V>> results = map.get(key);
if(results == null) {
return new ArrayList<Versioned<V>>(0);
} else {
return new ArrayList<Versioned<V>>(results);
}
}
@Override
public Map<K, List<Versioned<V>>> getAll(Iterable<K> keys, Map<K, T> transforms)
throws VoldemortException {
StoreUtils.assertValidKeys(keys);
return StoreUtils.getAll(this, keys, transforms);
}
@Override
public synchronized void put(K key, Versioned<V> value, T transforms) throws VoldemortException {
StoreUtils.assertValidKey(key);
List<Versioned<V>> items = map.get(key);
// If we have no value, add the current value
if(items == null) {
items = new ArrayList<Versioned<V>>();
}
// Check for existing versions - remember which items to
// remove in case of success
List<Versioned<V>> itemsToRemove = new ArrayList<Versioned<V>>(items.size());
for(Versioned<V> versioned: items) {
Occurred occurred = value.getVersion().compare(versioned.getVersion());
if(occurred == Occurred.BEFORE) {
throw new ObsoleteVersionException("Obsolete version for key '" + key + "': "
+ value.getVersion());
} else if(occurred == Occurred.AFTER) {
itemsToRemove.add(versioned);
}
}
items.removeAll(itemsToRemove);
items.add(value);
map.put(key, items);
}
@Override
public synchronized List<Versioned<V>> multiVersionPut(K key, final List<Versioned<V>> values) {
// TODO the day this class implements getAndLock and putAndUnlock, this
// method can be removed
StoreUtils.assertValidKey(key);
List<Versioned<V>> obsoleteVals = null;
List<Versioned<V>> valuesInStorage = null;
valuesInStorage = map.get(key);
if(valuesInStorage == null) {
valuesInStorage = new ArrayList<Versioned<V>>(values.size());
}
obsoleteVals = resolveAndConstructVersionsToPersist(valuesInStorage, values);
map.put(key, valuesInStorage);
return obsoleteVals;
}
@Override
public ClosableIterator<Pair<K, Versioned<V>>> entries() {
return new InMemoryIterator<K, V, T>(map, this);
}
@Override
public ClosableIterator<K> keys() {
// TODO Implement more efficient version.
return StoreUtils.keys(entries());
}
@Override
public ClosableIterator<Pair<K, Versioned<V>>> entries(int partition) {
throw new UnsupportedOperationException("Partition based entries scan not supported for this storage type");
}
@Override
public ClosableIterator<K> keys(int partition) {
throw new UnsupportedOperationException("Partition based key scan not supported for this storage type");
}
@Override
public synchronized void truncate() {
map.clear();
}
@Override
public String toString() {
return toString(15);
}
public String toString(int size) {
StringBuilder builder = new StringBuilder();
builder.append("{");
int count = 0;
for(Entry<K, List<Versioned<V>>> entry: map.entrySet()) {
if(count > size) {
builder.append("...");
break;
}
builder.append(entry.getKey());
builder.append(':');
builder.append(entry.getValue());
builder.append(',');
}
builder.append('}');
return builder.toString();
}
/**
* This class relies on the concurrent hash map's iterator to return a
* weakly consistent view of the data in the map.
*/
@NotThreadsafe
private static class InMemoryIterator<K, V, T> implements
ClosableIterator<Pair<K, Versioned<V>>> {
private final Iterator<Entry<K, List<Versioned<V>>>> iterator;
private K currentKey;
private Iterator<Versioned<V>> currentValues;
private InMemoryStorageEngine<K, V, T> inMemoryStorageEngine;
public InMemoryIterator(ConcurrentMap<K, List<Versioned<V>>> map,
InMemoryStorageEngine<K, V, T> inMemoryStorageEngine) {
this.iterator = map.entrySet().iterator();
this.inMemoryStorageEngine = inMemoryStorageEngine;
}
@Override
public boolean hasNext() {
return hasNextInCurrentValues() || iterator.hasNext();
}
private boolean hasNextInCurrentValues() {
return currentValues != null && currentValues.hasNext();
}
private Pair<K, Versioned<V>> nextInCurrentValues() {
Versioned<V> item = currentValues.next();
return Pair.create(currentKey, item);
}
@Override
public Pair<K, Versioned<V>> next() {
if(hasNextInCurrentValues()) {
return nextInCurrentValues();
} else {
// keep trying to get a next, until we find one (they could get
// removed)
while(true) {
Entry<K, List<Versioned<V>>> entry = iterator.next();
List<Versioned<V>> list = entry.getValue();
synchronized(this.inMemoryStorageEngine) {
// okay we may have gotten an empty list, if so try
// again
if(list.size() == 0)
continue;
// grab a snapshot of the list while we have exclusive
// access
currentValues = new ArrayList<Versioned<V>>(list).iterator();
}
currentKey = entry.getKey();
return nextInCurrentValues();
}
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("No removal y'all.");
}
@Override
public void close() {
// nothing to do here
}
}
}