/*
* 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 com.addthis.hydra.store.db;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeMap;
import com.addthis.basis.util.Parameter;
import com.addthis.codec.Codec;
import com.addthis.codec.binary.CodecBin2;
import com.addthis.codec.codables.BytesCodable;
import com.addthis.hydra.store.kv.ByteStore;
import com.addthis.hydra.store.kv.ByteStoreBDB;
import com.addthis.hydra.store.kv.MapDbByteStore;
import com.addthis.hydra.store.kv.PagedKeyValueStore;
import com.addthis.hydra.store.kv.ReadExternalPagedStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* wrapper around ReadExternalPagedStore
* <p/>
* It mostly provides two things:
* <p/>
* 1. encode / decode implementations
* 2. a data range object that notably has RIGHT-side bounded iteration (left as well
* but that is stolen from an iterator it wraps. There are like five nested iterators).
*/
public class ReadPageDB<V extends IReadWeighable & BytesCodable> implements IPageDB<DBKey, V> {
private static final Logger log = LoggerFactory.getLogger(ReadPageDB.class);
static final String defaultDbName = Parameter.value("pagedb.dbname", "db.key");
private static final Codec codec = CodecBin2.INSTANCE;
private final Class<? extends V> clazz;
private final ReadExternalPagedStore<DBKey, V> eps;
public ReadPageDB(File dir, Class<? extends V> clazz, int maxSize, int maxWeight)
throws IOException {
this(dir, clazz, maxSize, maxWeight, false);
}
public ReadPageDB(File dir, Class<? extends V> clazz, int maxSize,
int maxWeight, boolean metrics) throws IOException {
this.clazz = clazz;
String dbType = PageDB.getByteStoreNameForFile(dir);
ByteStore store;
switch (dbType) {
case PageDB.PAGED_MAP_DB:
store = new MapDbByteStore(dir, defaultDbName, true);
break;
case PageDB.PAGED_BERK_DB:
// fall through -- the previous dbType was always something like 'pagedb' so this
// is expected
default:
store = new ByteStoreBDB(dir, defaultDbName, true);
break;
}
this.eps = new ReadExternalPagedStore<>(new ReadDBKeyCoder<>(codec, clazz), store, maxSize,
maxWeight, metrics);
}
public String toString() {
return "PageDB:" + clazz + "," + eps;
}
@Override
public V get(DBKey key) {
return eps.getValue(key);
}
public TreeMap<DBKey, V> toTreeMap() {
try {
IPageDB.Range<DBKey, V> range =
range(this.eps.getFirstKey(), new DBKey(Long.MAX_VALUE, ""));
Iterator<Map.Entry<DBKey, V>> iterator = range.iterator();
TreeMap<DBKey, V> map = new TreeMap<>();
while (iterator.hasNext()) {
Map.Entry<DBKey, V> entry = iterator.next();
map.put(entry.getKey(), entry.getValue());
}
return map;
} catch (Exception e) {
log.warn("failed to dump PageDB to Map, returning empty (expected for uninitialized db)", e);
return new TreeMap<>();
}
}
//Uses the DR object from below.
@Override
public IPageDB.Range<DBKey, V> range(DBKey from, DBKey to) {
return new DR(from, to);
}
/**
* Close the source.
*
* @param cleanLog unused in the ReadPageDB implementation.
* @param operation unused in the ReadPageDB implementation.
* @return status code. A status code of 0 indicates success.
*/
@Override
public int close(boolean cleanLog, CloseOperation operation) {
close();
return 0;
}
@Override
public void close() {
eps.close();
}
/**
* Wraps eps range and adds RIGHT-side bounding (on top of the LEFT-side bounding
* provided by the eps object). Not thread safe.
*/
private class DR implements IPageDB.Range<DBKey, V>, Iterator<Map.Entry<DBKey, V>> {
private final Iterator<Map.Entry<DBKey, V>> iter;
private final DBKey to;
private Map.Entry<DBKey, V> next;
private DR(DBKey start, DBKey to) {
if (log.isDebugEnabled()) log.debug("DR(" + start + "-" + to + ")");
this.iter = eps.range(start);
this.to = to;
}
@Override
public boolean hasNext() {
if (next == null && iter.hasNext()) {
next = iter.next();
if (to != null && next.getKey().compareTo(to) >= 0) {
if (log.isDebugEnabled()) log.debug("stopping range b/c " + next.getKey() + " >= " + to);
close();
next = null;
}
}
return next != null;
}
@Override
public void close() {
}
@Override
public Map.Entry<DBKey, V> next() {
if (hasNext()) {
Map.Entry<DBKey, V> ret = next;
next = null;
return ret;
} else {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator<Map.Entry<DBKey, V>> iterator() {
return this;
}
}
//Unsupported interface compatability stuff
@Override public void setCacheSize(final int cachesize) {
throw new UnsupportedOperationException();
}
@Override public void setPageSize(int pagesize) {
throw new UnsupportedOperationException();
}
@Override public void setCacheMem(long maxmem) {
throw new UnsupportedOperationException();
}
@Override public void setPageMem(int maxmem) {
throw new UnsupportedOperationException();
}
@Override public void setMemSampleInterval(int sample) {
throw new UnsupportedOperationException();
}
@Override public V put(DBKey key, V value) {
throw new UnsupportedOperationException();
}
@Override public V remove(DBKey key) {
throw new UnsupportedOperationException();
}
@Override public void remove(DBKey start, DBKey end) {
throw new UnsupportedOperationException();
}
public ReadExternalPagedStore<DBKey, V> getReadEps() {
return eps;
}
@Override
public PagedKeyValueStore<DBKey, V> getEps() { throw new UnsupportedOperationException(); }
}