/*
* 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 org.sdnplatform.sync.internal.store;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.sdnplatform.sync.IClosableIterator;
import org.sdnplatform.sync.IVersion;
import org.sdnplatform.sync.Versioned;
import org.sdnplatform.sync.IVersion.Occurred;
import org.sdnplatform.sync.error.SyncException;
import org.sdnplatform.sync.internal.version.VectorClock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* Group of store utilities
*
*/
public class StoreUtils {
protected static final Logger logger =
LoggerFactory.getLogger(StoreUtils.class);
public static void assertValidKeys(Iterable<?> keys) {
if(keys == null)
throw new IllegalArgumentException("Keys cannot be null.");
for(Object key: keys)
assertValidKey(key);
}
public static <K> void assertValidKey(K key) {
if(key == null)
throw new IllegalArgumentException("Key cannot be null.");
}
/**
* Implements getAll by delegating to get.
* @throws SyncException
*/
public static <K, V> Map<K, List<Versioned<V>>>
getAll(IStore<K, V> storageEngine,
Iterable<K> keys) throws SyncException {
Map<K, List<Versioned<V>>> result = newEmptyHashMap(keys);
for(K key: keys) {
List<Versioned<V>> value =
storageEngine.get(key);
if(!value.isEmpty())
result.put(key, value);
}
return result;
}
/**
* Returns an empty map with expected size matching the iterable size if
* it's of type Collection. Otherwise, an empty map with the default size is
* returned.
*/
public static <K, V> HashMap<K, V> newEmptyHashMap(Iterable<?> iterable) {
if(iterable instanceof Collection<?>)
return Maps.newHashMapWithExpectedSize(((Collection<?>) iterable).size());
return Maps.newHashMap();
}
/**
* Closes a Closeable and logs a potential error instead of re-throwing the
* exception. If {@code null} is passed, this method is a no-op.
*
* This is typically used in finally blocks to prevent an exception thrown
* during close from hiding an exception thrown inside the try.
*
* @param c The Closeable to close, may be null.
*/
public static void close(Closeable c) {
if(c != null) {
try {
c.close();
} catch(IOException e) {
logger.error("Error closing stream", e);
}
}
}
public static <V> List<IVersion> getVersions(List<Versioned<V>> versioneds) {
List<IVersion> versions = Lists.newArrayListWithCapacity(versioneds.size());
for(Versioned<?> versioned: versioneds)
versions.add(versioned.getVersion());
return versions;
}
public static <K, V> IClosableIterator<K>
keys(final IClosableIterator<Entry<K, V>> values) {
return new IClosableIterator<K>() {
public void close() {
values.close();
}
public boolean hasNext() {
return values.hasNext();
}
public K next() {
Entry<K, V> value = values.next();
if(value == null)
return null;
return value.getKey();
}
public void remove() {
values.remove();
}
};
}
public static <V> boolean canDelete(List<Versioned<V>> items,
long tombstoneDeletion) {
List<VectorClock> tombstones = new ArrayList<VectorClock>();
long now = System.currentTimeMillis();
// make two passes; first we find tombstones that are old enough.
for (Versioned<V> v : items) {
if (v.getValue() == null) {
VectorClock vc = (VectorClock)v.getVersion();
if ((vc.getTimestamp() + tombstoneDeletion) < now)
tombstones.add(vc);
}
}
// second, if we find a tombstone which is later than every
// non-tombstone value, then we can delete the key.
for (VectorClock vc : tombstones) {
boolean later = true;
for (Versioned<V> v : items) {
if (v.getValue() != null) {
VectorClock curvc = (VectorClock)v.getVersion();
if (!Occurred.AFTER.equals(vc.compare(curvc))) {
later = false;
break;
}
}
}
if (later) {
// we found a tombstone that's old enough and
// logically later than all non-tombstones. We can
// remove the value from the map.
return true;
}
}
return false;
}
}