package gov.nysenate.openleg.dao.bill.reference.senatesite;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import gov.nysenate.openleg.config.Environment;
import gov.nysenate.openleg.model.spotcheck.SpotCheckRefType;
import gov.nysenate.openleg.model.spotcheck.senatesite.SenateSiteDump;
import gov.nysenate.openleg.model.spotcheck.senatesite.SenateSiteDumpFragment;
import gov.nysenate.openleg.model.spotcheck.senatesite.SenateSiteDumpId;
import gov.nysenate.openleg.util.DateUtils;
import gov.nysenate.openleg.util.FileIOUtils;
import gov.nysenate.openleg.util.SenateSiteDumpFragParser;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Pattern;
import static gov.nysenate.openleg.util.DateUtils.*;
@Repository
public class FsSenateSiteDao implements SenateSiteDao {
private static final Logger logger = LoggerFactory.getLogger(FsSenateSiteDao.class);
@Autowired private Environment environment;
@Autowired private SenateSiteDumpFragParser parser;
@Autowired private ObjectMapper objectMapper;
public static final String SENSITE_DUMP_DIRNAME = "sensite-dump";
private static final String DUMP_FRAG_FILENAME_PREFIX_TEMPL = "_dump-${fromDateTime}-${toDateTime}-";
private static final String DUMP_FRAG_FILENAME_TEMPL = "${seqNo}.json";
/** --- Implemented Methods --- */
@Override
public Collection<SenateSiteDump> getPendingDumps(SpotCheckRefType refType) throws IOException {
Collection<File> fragmentFiles =
FileUtils.listFiles(getIncomingDumpDir(refType), new RegexFileFilter(dumpFragFilenameRegex(refType)), null);
List<SenateSiteDumpFragment> fragments = new LinkedList<>();
for(File file : fragmentFiles) {
fragments.add(getFragmentFromFile(file, refType));
}
return groupFragmentsIntoDumps(fragments);
}
private Collection<SenateSiteDump> groupFragmentsIntoDumps(Collection<SenateSiteDumpFragment> fragments) {
Map<SenateSiteDumpId, SenateSiteDump> dumpMap = new HashMap<>();
fragments.stream().forEach(fragment -> {
if (!dumpMap.containsKey(fragment.getDumpId())) {
SenateSiteDump dump = new SenateSiteDump(fragment.getDumpId());
dump.addDumpFragment(fragment);
dumpMap.put(fragment.getDumpId(), dump);
}
dumpMap.get(fragment.getDumpId()).addDumpFragment(fragment);
});
return dumpMap.values();
}
@Override
public void saveDumpFragment(SenateSiteDumpFragment fragment, String fragmentData) throws IOException {
File fragmentFile = new File(getIncomingDumpDir(fragment.getDumpId().getRefType()), getDumpFragFilename(fragment));
logger.info("saving senate site dump fragment {}", fragmentFile.getAbsolutePath());
FileUtils.write(fragmentFile, prettyPrintJson(fragmentData), Charset.forName("UTF-8"));
}
private String prettyPrintJson(String fragmentData) throws IOException {
Object json = objectMapper.readValue(fragmentData, Object.class);
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
}
@Override
public void setProcessed(SenateSiteDump dump) throws IOException {
for(SenateSiteDumpFragment fragment : dump.getDumpFragments()) {
File fragFile = fragment.getFragmentFile();
try {
FileUtils.moveFileToDirectory(fragFile, getArchiveBillDir(dump.getDumpId().getRefType()), true);
} catch (FileExistsException ex) {
File destFile = new File(getArchiveBillDir(dump.getDumpId().getRefType()), fragFile.getName());
logger.warn("attempting to overwrite " + destFile.getAbsolutePath());
destFile.delete();
FileUtils.moveFileToDirectory(fragFile, getArchiveBillDir(dump.getDumpId().getRefType()), true);
}
}
}
/** --- Internal Methods --- */
/**
* Parse dump fragment metadata from a fragment json file
*/
private SenateSiteDumpFragment getFragmentFromFile(File fragFile, SpotCheckRefType refType) throws IOException {
SenateSiteDumpFragment fragment = parser.parseFragment(FileUtils.readFileToString(fragFile, "UTF-8"), refType);
fragment.setFragmentFile(fragFile);
return fragment;
}
/**
* @param fragment SenateSiteDumpFragment
* @return String - the prefix that is used for all dump fragments of the designated dump
*/
private static String getDumpFragFilenamePrefix(SenateSiteDumpFragment fragment) {
return StrSubstitutor.replace(dumpFragFilenamePrefix(fragment.getDumpId().getRefType()), getDumpIdSubMap(fragment.getDumpId()));
}
/**
* @param fragment SenateSiteDumpFragment
* @return String - the filename that is used for the designated dump fragment
*/
private static String getDumpFragFilename(SenateSiteDumpFragment fragment) {
return StrSubstitutor.replace(dumpFragFilename(fragment.getDumpId().getRefType()), getDumpFragSubMap(fragment));
}
private static ImmutableMap<String, String> getDumpIdSubMap(SenateSiteDumpId dumpId) {
return ImmutableMap.of(
"fromDateTime", DateUtils.startOfDateTimeRange(dumpId.getRange()).format(BASIC_ISO_DATE_TIME),
"toDateTime", DateUtils.endOfDateTimeRange(dumpId.getRange()).format(BASIC_ISO_DATE_TIME));
}
private static ImmutableMap<String, String> getDumpFragSubMap(SenateSiteDumpFragment fragment) {
return ImmutableMap.<String, String>builder()
.putAll(getDumpIdSubMap(fragment.getDumpId()))
.put("seqNo", Integer.toString(fragment.getSequenceNo()))
.build();
}
/** Directory where new dumps are placed. */
private File getIncomingDumpDir(SpotCheckRefType refType) throws IOException {
return FileIOUtils.safeGetFolder(environment.getStagingDir(), SENSITE_DUMP_DIRNAME + "/" + refType.getRefName());
}
/** Directory where dumps that have been processed are stored. */
private File getArchiveBillDir(SpotCheckRefType refType) throws IOException {
return FileIOUtils.safeGetFolder(environment.getArchiveDir(), SENSITE_DUMP_DIRNAME + "/" + refType.getRefName());
}
/** Prefix for naming SenateSiteDumpFragment files. */
private static String dumpFragFilenamePrefix(SpotCheckRefType refType) {
return refType.getRefName() + DUMP_FRAG_FILENAME_PREFIX_TEMPL;
}
/** Full filename for SenateSiteDumpFragment files. */
private static String dumpFragFilename(SpotCheckRefType refType) {
return dumpFragFilenamePrefix(refType) + DUMP_FRAG_FILENAME_TEMPL;
}
/** Regex to match SenateSiteDumpFragments of the given refType. */
private static Pattern dumpFragFilenameRegex(SpotCheckRefType refType) {
return Pattern.compile(
StrSubstitutor.replace(dumpFragFilename(refType), ImmutableMap.of(
"fromDateTime", "(" + BASIC_ISO_DATE_TIME_REGEX.toString() + ")",
"toDateTime", "(" + BASIC_ISO_DATE_TIME_REGEX.toString() + ")",
"seqNo", "(\\d+)")));
}
}