package voldemort.store.views; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import voldemort.VoldemortException; import voldemort.annotations.Experimental; import voldemort.serialization.Serializer; import voldemort.store.AbstractStorageEngine; import voldemort.store.StorageEngine; import voldemort.store.Store; import voldemort.store.StoreCapabilityType; import voldemort.store.StoreUtils; import voldemort.store.compress.CompressionStrategy; import voldemort.store.serialized.SerializingStore; import voldemort.utils.ByteArray; import voldemort.utils.ClosableIterator; import voldemort.utils.Pair; import voldemort.utils.Utils; import voldemort.versioning.Version; import voldemort.versioning.Versioned; import com.google.common.collect.AbstractIterator; /** * Views are transformations of other stores * * */ @Experimental public class ViewStorageEngine extends AbstractStorageEngine<ByteArray, byte[], byte[]> { private final Store<Object, Object, Object> serializingStore; private final StorageEngine<ByteArray, byte[], byte[]> target; private final Serializer<Object> valSerializer; private final Serializer<Object> transformSerializer; private final Serializer<Object> targetKeySerializer; private final Serializer<Object> targetValSerializer; private final View<Object, Object, Object, Object> view; private final CompressionStrategy valueCompressionStrategy; @SuppressWarnings("unchecked") public ViewStorageEngine(String name, StorageEngine<ByteArray, byte[], byte[]> target, Serializer<?> valSerializer, Serializer<?> transformSerializer, Serializer<?> targetKeySerializer, Serializer<?> targetValSerializer, CompressionStrategy valueCompressionStrategy, View<?, ?, ?, ?> valueTrans) { super(name); this.target = Utils.notNull(target); this.serializingStore = new SerializingStore(target, targetKeySerializer, targetValSerializer, null); this.valSerializer = (Serializer<Object>) valSerializer; this.transformSerializer = (Serializer<Object>) transformSerializer; this.targetKeySerializer = (Serializer<Object>) targetKeySerializer; this.targetValSerializer = (Serializer<Object>) targetValSerializer; this.view = (View<Object, Object, Object, Object>) valueTrans; this.valueCompressionStrategy = valueCompressionStrategy; if(valueTrans == null) throw new IllegalArgumentException("View without either a key transformation or a value transformation."); } private List<Versioned<byte[]>> inflateValues(List<Versioned<byte[]>> result) { List<Versioned<byte[]>> inflated = new ArrayList<Versioned<byte[]>>(result.size()); for(Versioned<byte[]> item: result) { inflated.add(inflateValue(item)); } return inflated; } private List<Versioned<byte[]>> deflateValues(List<Versioned<byte[]>> values) { List<Versioned<byte[]>> deflated = new ArrayList<Versioned<byte[]>>(values.size()); for(Versioned<byte[]> item: values) { deflated.add(deflateValue(item)); } return deflated; } private Versioned<byte[]> deflateValue(Versioned<byte[]> versioned) throws VoldemortException { byte[] deflatedData = null; try { deflatedData = valueCompressionStrategy.deflate(versioned.getValue()); } catch(IOException e) { throw new VoldemortException(e); } return new Versioned<byte[]>(deflatedData, versioned.getVersion()); } private Versioned<byte[]> inflateValue(Versioned<byte[]> versioned) throws VoldemortException { byte[] inflatedData = null; try { inflatedData = valueCompressionStrategy.inflate(versioned.getValue()); } catch(IOException e) { throw new VoldemortException(e); } return new Versioned<byte[]>(inflatedData, versioned.getVersion()); } @Override public boolean delete(ByteArray key, Version version) throws VoldemortException { return target.delete(key, version); } @Override public List<Versioned<byte[]>> get(ByteArray key, byte[] transforms) throws VoldemortException { List<Versioned<byte[]>> values = target.get(key, null); List<Versioned<byte[]>> results = new ArrayList<Versioned<byte[]>>(); if(valueCompressionStrategy != null) values = inflateValues(values); for(Versioned<byte[]> v: values) { results.add(new Versioned<byte[]>(valueToViewSchema(key, v.getValue(), transforms), v.getVersion())); } if(valueCompressionStrategy != null) results = deflateValues(results); return results; } @Override public Map<ByteArray, List<Versioned<byte[]>>> getAll(Iterable<ByteArray> keys, Map<ByteArray, byte[]> transforms) throws VoldemortException { return StoreUtils.getAll(this, keys, transforms); } @Override public List<Version> getVersions(ByteArray key) { return target.getVersions(key); } @Override public void put(ByteArray key, Versioned<byte[]> value, byte[] transforms) throws VoldemortException { if(valueCompressionStrategy != null) value = inflateValue(value); Versioned<byte[]> result = Versioned.value(valueFromViewSchema(key, value.getValue(), transforms), value.getVersion()); if(valueCompressionStrategy != null) result = deflateValue(result); target.put(key, result, null); } @Override public ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> entries() { return new ViewIterator(target.entries()); } @Override public ClosableIterator<ByteArray> keys() { return StoreUtils.keys(entries()); } @Override public ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> entries(int partition) { return new ViewIterator(target.entries(partition)); } @Override public ClosableIterator<ByteArray> keys(int partition) { return StoreUtils.keys(entries(partition)); } @Override public void truncate() { ViewIterator iterator = new ViewIterator(target.entries()); while(iterator.hasNext()) { Pair<ByteArray, Versioned<byte[]>> pair = iterator.next(); target.delete(pair.getFirst(), pair.getSecond().getVersion()); } } @Override public Object getCapability(StoreCapabilityType capability) { if(capability == StoreCapabilityType.VIEW_TARGET) return this.target; else return super.getCapability(capability); // will result in a NoSuchCapabilityException thrown } // public void close() throws VoldemortException {} private byte[] valueFromViewSchema(ByteArray key, byte[] value, byte[] transforms) { return this.targetValSerializer.toBytes(this.view.viewToStore(this.serializingStore, this.targetKeySerializer.toObject(key.get()), this.valSerializer.toObject(value), (transformSerializer != null && transforms != null) ? this.transformSerializer.toObject(transforms) : null)); } private byte[] valueToViewSchema(ByteArray key, byte[] value, byte[] transforms) { return this.valSerializer.toBytes(this.view.storeToView(this.serializingStore, this.targetKeySerializer.toObject(key.get()), this.targetValSerializer.toObject(value), (transformSerializer != null && transforms != null) ? this.transformSerializer.toObject(transforms) : null)); } private class ViewIterator extends AbstractIterator<Pair<ByteArray, Versioned<byte[]>>> implements ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> { private final ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> inner; public ViewIterator(ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> inner) { this.inner = inner; } @Override public void close() { this.inner.close(); } @Override protected Pair<ByteArray, Versioned<byte[]>> computeNext() { Pair<ByteArray, Versioned<byte[]>> p = inner.next(); Versioned<byte[]> newVal = Versioned.value(valueToViewSchema(p.getFirst(), p.getSecond().getValue(), null), p.getSecond().getVersion()); return Pair.create(p.getFirst(), newVal); } } @Override public boolean isPartitionAware() { return target.isPartitionAware(); } @Override public boolean isPartitionScanSupported() { return target.isPartitionScanSupported(); } }