package com.scopely.infrastructure.kinesis;
import com.amazonaws.services.kinesis.connectors.KinesisConnectorConfiguration;
import com.amazonaws.services.kinesis.connectors.UnmodifiableBuffer;
import com.amazonaws.services.kinesis.connectors.interfaces.IEmitter;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Based on {@link com.amazonaws.services.kinesis.connectors.s3.S3Emitter}, but allows for the injection of a specific
* instance of {@link AmazonS3}, making testing more straightforward.
*/
public class InjectableS3Emitter implements IEmitter<byte[]> {
private static final Logger LOGGER = LoggerFactory.getLogger(S3RecorderPipeline.class);
private final KinesisConnectorConfiguration configuration;
private final AmazonS3 s3;
protected final String s3Bucket;
public InjectableS3Emitter(KinesisConnectorConfiguration configuration, AmazonS3 amazonS3) {
this.configuration = configuration;
s3Bucket = configuration.S3_BUCKET;
this.s3 = amazonS3;
}
protected String getS3FileName(String firstSeq, String lastSeq) {
return String.format(Locale.US, "%s/%s/%s-%s",
configuration.KINESIS_INPUT_STREAM,
Clock.systemUTC().instant().atOffset(ZoneOffset.UTC).format(S3RecorderPipeline.FORMATTER),
firstSeq, lastSeq);
}
protected String getS3URI(String s3FileName) {
return "s3://" + s3Bucket + "/" + s3FileName;
}
@Override
public List<byte[]> emit(final UnmodifiableBuffer<byte[]> buffer) throws IOException {
List<byte[]> records = buffer.getRecords();
// Write all of the records to a compressed output stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (byte[] record : records) {
try {
baos.write(record);
} catch (Exception e) {
LOGGER.error("Error writing record to output stream. Failing this emit attempt. Record: "
+ Arrays.toString(record),
e);
return buffer.getRecords();
}
}
// Get the Amazon S3 filename
String s3FileName = getS3FileName(buffer.getFirstSequenceNumber(), buffer.getLastSequenceNumber());
String s3URI = getS3URI(s3FileName);
try {
ByteArrayInputStream object = new ByteArrayInputStream(baos.toByteArray());
LOGGER.debug("Starting upload of file " + s3URI + " to Amazon S3 containing " + records.size() + " records.");
ObjectMetadata meta = new ObjectMetadata();
meta.setContentLength(baos.size());
s3.putObject(s3Bucket, s3FileName, object, meta);
LOGGER.info("Successfully emitted " + buffer.getRecords().size() + " records to Amazon S3 in " + s3URI);
return Collections.emptyList();
} catch (Exception e) {
LOGGER.error("Caught exception when uploading file " + s3URI + "to Amazon S3. Failing this emit attempt.", e);
return buffer.getRecords();
}
}
@Override
public void fail(List<byte[]> records) {
for (byte[] record : records) {
LOGGER.error("Record failed: " + Arrays.toString(record));
}
}
@Override
public void shutdown() {
// don't shut down the S3 client, since it might be shared.
}
}