package com.scopely.infrastructure.kinesis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import rx.Observable; import rx.Producer; import rx.Subscriber; /** * A version of {@link rx.internal.operators.OperatorBufferWithSize} which buffers {@link ByteBuffer}s * that don't exceeded the configured count and size. */ public class OperatorBufferKinesisBatch implements Observable.Operator<List<ByteBuffer>, ByteBuffer> { private static final Logger LOGGER = LoggerFactory.getLogger(OperatorBufferKinesisBatch.class); final int maxCount; final int maxSize; /** * @param maxCount the number of elements a buffer should have before being emitted * @param maxSize max size in bytes of each list of ByteBuffers. */ public OperatorBufferKinesisBatch(int maxCount, int maxSize) { if (maxCount <= 0) { throw new IllegalArgumentException("maxCount must be greater than 0"); } if (maxSize <= 0) { throw new IllegalArgumentException("maxSize must be greater than 0"); } this.maxCount = maxCount; this.maxSize = maxSize; } @Override public Subscriber<? super ByteBuffer> call(final Subscriber<? super List<ByteBuffer>> child) { return new Subscriber<ByteBuffer>(child) { List<ByteBuffer> buffer; AtomicInteger totalBufferSize; @Override public void setProducer(final Producer producer) { child.setProducer(new Producer() { private volatile boolean infinite = false; @Override public void request(long n) { if (infinite) { return; } if (n >= Long.MAX_VALUE / maxCount) { // n == Long.MAX_VALUE or n * maxCount >= Long.MAX_VALUE infinite = true; producer.request(Long.MAX_VALUE); } else { producer.request(n * maxCount); } } }); } @Override public void onNext(ByteBuffer bytes) { if (buffer == null) { buffer = new ArrayList<>(maxCount); totalBufferSize = new AtomicInteger(); } int currentBufferSize = bytes.limit(); boolean reachedSizeLimit = totalBufferSize.get() + currentBufferSize > maxSize; if (reachedSizeLimit && buffer.isEmpty()) { LOGGER.warn("Dropping single ByteBuffer which was too big for the configured max size."); return; } if (!reachedSizeLimit) { totalBufferSize.addAndGet(currentBufferSize); buffer.add(bytes); } if (reachedSizeLimit || buffer.size() == maxCount) { List<ByteBuffer> oldBuffer = buffer; buffer = null; child.onNext(oldBuffer); } } @Override public void onError(Throwable e) { buffer = null; totalBufferSize = null; child.onError(e); } @Override public void onCompleted() { List<ByteBuffer> oldBuffer = buffer; buffer = null; if (oldBuffer != null) { try { child.onNext(oldBuffer); } catch (Throwable t) { onError(t); return; } } child.onCompleted(); } }; } }