package com.jivesoftware.os.amza.service; import com.google.common.collect.Maps; import com.jivesoftware.os.amza.api.TimestampedValue; import com.jivesoftware.os.amza.api.partition.Consistency; import com.jivesoftware.os.amza.api.partition.PartitionName; import com.jivesoftware.os.amza.api.ring.RingMember; import com.jivesoftware.os.amza.api.stream.ClientUpdates; import com.jivesoftware.os.amza.api.stream.KeyValueTimestampStream; import com.jivesoftware.os.amza.api.stream.TxKeyValueStream; import com.jivesoftware.os.amza.api.stream.UnprefixedWALKeys; import com.jivesoftware.os.amza.api.take.TakeCursors; import com.jivesoftware.os.amza.api.take.TakeResult; import com.jivesoftware.os.amza.api.wal.WALHighwater; import com.jivesoftware.os.amza.service.Partition.ScanRange; import com.jivesoftware.os.mlogger.core.MetricLogger; import com.jivesoftware.os.mlogger.core.MetricLoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * @author jonathan.colt */ public class EmbeddedClientProvider { // Aka Partition Client Provider private static final MetricLogger LOG = MetricLoggerFactory.getLogger(); private final PartitionProvider partitionProvider; public EmbeddedClientProvider(PartitionProvider partitionProvider) { this.partitionProvider = partitionProvider; } public EmbeddedClient getClient(PartitionName partitionName, CheckOnline checkOnline) { return new EmbeddedClient(partitionName, checkOnline); } public enum CheckOnline { never, once, always } public class EmbeddedClient { // Aka Partition Client private final PartitionName partitionName; private final CheckOnline checkOnline; private final AtomicBoolean requiresOnline; public EmbeddedClient(PartitionName partitionName, CheckOnline checkOnline) { this.partitionName = partitionName; this.checkOnline = checkOnline; this.requiresOnline = new AtomicBoolean(checkOnline != CheckOnline.never); } public void commit(Consistency consistency, byte[] prefix, ClientUpdates clientUpdates, long timeout, TimeUnit timeUnit) throws Exception { Partition partition = partitionProvider.getPartition(partitionName); partition.commit(consistency, prefix, clientUpdates, timeUnit.toMillis(timeout)); } public void get(Consistency consistency, byte[] prefix, UnprefixedWALKeys keys, KeyValueTimestampStream valuesStream) throws Exception { // TODO impl quorum reads? partitionProvider.getPartition(partitionName).get(consistency, prefix, requiresOnline.get(), keys, (prefix1, key, value, valueTimestamp, valueTombstoned, valueVersion) -> { if (valueTimestamp == -1 || valueTombstoned) { return valuesStream.stream(prefix1, key, null, -1, -1); } else { return valuesStream.stream(prefix1, key, value, valueTimestamp, valueVersion); } }); if (checkOnline == CheckOnline.once) { requiresOnline.set(false); } } public byte[] getValue(Consistency consistency, byte[] prefix, byte[] key) throws Exception { TimestampedValue timestampedValue = getTimestampedValue(consistency, prefix, key); return timestampedValue != null ? timestampedValue.getValue() : null; } public TimestampedValue getTimestampedValue(Consistency consistency, byte[] prefix, byte[] key) throws Exception { final TimestampedValue[] r = new TimestampedValue[1]; get(consistency, prefix, stream -> stream.stream(key), (_prefix, _key, value, timestamp, version) -> { if (timestamp != -1) { r[0] = new TimestampedValue(timestamp, version, value); } return true; }); return r[0]; } public void scan(Iterable<ScanRange> ranges, KeyValueTimestampStream stream, boolean hydrateValues) throws Exception { partitionProvider.getPartition(partitionName).scan( keyRangeStream -> { for (ScanRange range : ranges) { if (!keyRangeStream.stream(range.fromPrefix, range.fromKey, range.toPrefix, range.toKey)) { return false; } } return true; }, hydrateValues, requiresOnline.get(), (prefix, key, value, valueTimestamp, valueTombstoned, valueVersion) -> { return valueTombstoned || stream.stream(prefix, key, value, valueTimestamp, valueVersion); }); if (checkOnline == CheckOnline.once) { requiresOnline.set(false); } } public TakeCursors takeFromTransactionId(long transactionId, TxKeyValueStream stream) throws Exception { Map<RingMember, Long> ringMemberToMaxTxId = Maps.newHashMap(); TakeResult takeResult = partitionProvider.getPartition(partitionName).takeFromTransactionId(transactionId, requiresOnline.get(), (highwater) -> { for (WALHighwater.RingMemberHighwater memberHighwater : highwater.ringMemberHighwater) { ringMemberToMaxTxId.merge(memberHighwater.ringMember, memberHighwater.transactionId, Math::max); } }, stream); if (checkOnline == CheckOnline.once) { requiresOnline.set(false); } boolean tookToEnd = false; if (takeResult.tookToEnd != null) { tookToEnd = true; for (WALHighwater.RingMemberHighwater highwater : takeResult.tookToEnd.ringMemberHighwater) { ringMemberToMaxTxId.merge(highwater.ringMember, highwater.transactionId, Math::max); } } ringMemberToMaxTxId.merge(takeResult.tookFrom, takeResult.lastTxId, Math::max); List<TakeCursors.RingMemberCursor> cursors = new ArrayList<>(); for (Map.Entry<RingMember, Long> entry : ringMemberToMaxTxId.entrySet()) { cursors.add(new TakeCursors.RingMemberCursor(entry.getKey(), entry.getValue())); } return new TakeCursors(cursors, tookToEnd); } public TakeCursors takeFromTransactionId(byte[] prefix, long transactionId, TxKeyValueStream stream) throws Exception { Map<RingMember, Long> ringMemberToMaxTxId = Maps.newHashMap(); TakeResult takeResult = partitionProvider.getPartition(partitionName).takePrefixFromTransactionId(prefix, transactionId, requiresOnline.get(), (highwater) -> { for (WALHighwater.RingMemberHighwater memberHighwater : highwater.ringMemberHighwater) { ringMemberToMaxTxId.merge(memberHighwater.ringMember, memberHighwater.transactionId, Math::max); } }, stream); if (checkOnline == CheckOnline.once) { requiresOnline.set(false); } boolean tookToEnd = false; if (takeResult.tookToEnd != null) { tookToEnd = true; for (WALHighwater.RingMemberHighwater highwater : takeResult.tookToEnd.ringMemberHighwater) { ringMemberToMaxTxId.merge(highwater.ringMember, highwater.transactionId, Math::max); } } ringMemberToMaxTxId.merge(takeResult.tookFrom, takeResult.lastTxId, Math::max); List<TakeCursors.RingMemberCursor> cursors = new ArrayList<>(); for (Map.Entry<RingMember, Long> entry : ringMemberToMaxTxId.entrySet()) { cursors.add(new TakeCursors.RingMemberCursor(entry.getKey(), entry.getValue())); } return new TakeCursors(cursors, tookToEnd); } } }