package com.socrata.datasync.config.controlfile; import com.socrata.datasync.PublishMethod; import com.socrata.datasync.Utils; import com.socrata.datasync.job.IntegrationJob; import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.annotate.JsonPropertyOrder; import org.codehaus.jackson.map.annotate.JsonSerialize; import java.util.HashMap; @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) @JsonIgnoreProperties(ignoreUnknown=true) @JsonPropertyOrder(alphabetic=true) public class ControlFile { public String action; public String opaque; public FileTypeControl csv; public FileTypeControl tsv; public Boolean replacePreviousQueued; // NB: when using a mapper to read this class, you must enable // DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, if either of the timestamp formats // in csvControl or tsvControl are strings, rather than arrays of strings. public ControlFile() {} public ControlFile(String action, String opaque, FileTypeControl csvControl, FileTypeControl tsvControl, Boolean replacePreviousQueued) { this.action = action; this.opaque = opaque; this.csv = csvControl; this.tsv = tsvControl; this.replacePreviousQueued = replacePreviousQueued; } // Hack city. Apparently the ControlFile must have both a csv and a tsv control file, even though only one may be // active at any given time. The control file editor will be manipulating the active one, regardless of type. // Thus, I need to return the active FTC here that can later be manipulated throughout the model. @JsonIgnore public FileTypeControl getFileTypeControl(){ if (csv != null) return csv; else return tsv; } @JsonIgnore public FileTypeControl getCsvFtc(){ return csv; } @JsonIgnore public FileTypeControl getTsvFtc(){ return tsv; } /** * Generates the default ControlFile object based on given job parameters * * @param fileToPublish filename of file to publish (.tsv or .csv file) * @param publishMethod to use to publish (upsert, append, replace, or delete) * NOTE: this option will be overriden if this control file is for an ftp job * @param columns the column headers corresponding to the filetoPublish, needed if it lacks headers * @param useSocrataGeocoding if true use Socrata's geocoding to geocode Location columns * @return content of control.json based on given job parameters */ public static ControlFile generateControlFile(final String fileToPublish, final PublishMethod publishMethod, final String[] columns, final boolean useSocrataGeocoding, final boolean hasHeaderRow) { String fileToPublishExtension = Utils.getFileExtension(fileToPublish); boolean isCsv = fileToPublishExtension.equalsIgnoreCase("csv"); String quote = isCsv ? "\"" : "\u0000"; FileTypeControl ftc = new FileTypeControl() .columns(columns) .encoding("utf-8") .hasHeaderRow(hasHeaderRow) .quote(quote); int skip = 0; String separator = isCsv ? "," : "\t"; //Adding our standard export formats so that a customer can easily round-trip data into the system. String[] timeFormats = new String[]{"ISO8601", "MM/dd/yy", "MM/dd/yyyy", "dd-MMM-yyyy","MM/dd/yyyy hh:mm:ss a Z","MM/dd/yyyy hh:mm:ss a"}; ftc.emptyTextIsNull(true) .filePath(fileToPublish) .ignoreColumns(new String[]{}) .fixedTimestampFormat(timeFormats) .floatingTimestampFormat(timeFormats) .separator(separator) .skip(skip) .timezone("UTC") .useSocrataGeocoding(useSocrataGeocoding) .trimWhitespace(true) .trimServerWhitespace(false) .overrides(new HashMap<String, ColumnOverride>()) .setAsideErrors(false); // for replace jobs, calculate column statistics if (PublishMethod.replace.equals(publishMethod)) ftc.columnStatistics(true); if (isCsv) { return new ControlFile(Utils.capitalizeFirstLetter(publishMethod.name()), null, ftc, null, null); } else { return new ControlFile(Utils.capitalizeFirstLetter(publishMethod.name()), null, null, ftc, null); } } public String generateAndAddOpaqueUUID() { String uuid = Utils.generateRequestId(); this.opaque = uuid; return uuid; } }