package com.airbnb.airpal.api.output.builders; import com.opencsv.CSVWriter; import com.facebook.presto.client.Column; import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.Lists; import com.google.common.io.CountingOutputStream; import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.List; import java.util.UUID; import java.util.zip.GZIPOutputStream; @Slf4j public class CsvOutputBuilder implements JobOutputBuilder { private static final String FILE_SUFFIX = ".csv"; @JsonIgnore private final File outputFile; @JsonIgnore private final CSVWriter csvWriter; @JsonIgnore private final boolean includeHeader; @JsonIgnore private final CountingOutputStream countingOutputStream; @JsonIgnore private final long maxFileSizeBytes; @JsonIgnore private boolean headerWritten = false; @JsonIgnore private final UUID jobUUID; public CsvOutputBuilder(boolean includeHeader, UUID jobUUID, long maxFileSizeBytes, boolean compressedOutput) throws IOException { this.includeHeader = includeHeader; this.jobUUID = jobUUID; this.outputFile = File.createTempFile(jobUUID.toString(), FILE_SUFFIX); this.maxFileSizeBytes = maxFileSizeBytes; this.countingOutputStream = new CountingOutputStream(new FileOutputStream(this.outputFile)); OutputStreamWriter writer; if (compressedOutput) { writer = new OutputStreamWriter(new GZIPOutputStream(this.countingOutputStream)); } else { writer = new OutputStreamWriter(this.countingOutputStream); } this.csvWriter = new CSVWriter(writer); } @Override public void addRow(List<Object> row) throws FileTooLargeException { final String[] values = new String[row.size()]; for (int i = 0; i < values.length; i++) { final Object value = row.get(i); values[i] = (value == null) ? "" : value.toString(); } writeCsvRow(values); } @Override public void addColumns(List<Column> columns) throws FileTooLargeException { if (!headerWritten && includeHeader) { List<String> columnNames = Lists.transform(columns, Column::getName); writeCsvRow(columnNames.toArray(new String[columnNames.size()])); headerWritten = true; } } @Override public String processQuery(String query) { return query; } private void writeCsvRow(String[] cols) throws FileTooLargeException { csvWriter.writeNext(cols); if (countingOutputStream.getCount() > maxFileSizeBytes) { try { csvWriter.close(); } catch (IOException e) { log.error("Caught exception closing csv writer", e); } delete(); throw new FileTooLargeException(); } } @Override public File build() { try { csvWriter.close(); } catch (IOException e) { e.printStackTrace(); } return outputFile; } @Override public void delete() { log.info("Deleting outputFile {}", outputFile); if (!outputFile.delete()) { log.error("Failed to delete outputFile {}", outputFile); } } }