package gov.nysenate.openleg.processor.daybreak; import gov.nysenate.openleg.dao.bill.reference.daybreak.DaybreakDao; import gov.nysenate.openleg.model.spotcheck.daybreak.*; import gov.nysenate.openleg.util.OpenlegThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; @Service public class ManagedDaybreakProcessService implements DaybreakProcessService{ private static Logger logger = LoggerFactory.getLogger(ManagedDaybreakProcessService.class); @Autowired private DaybreakDao daybreakDao; private ThreadFactory threadFactory = new OpenlegThreadFactory("daybreak-process"); /** --- Interfaced Methods --- */ @Override public int collate() { return collateDaybreakReports(); } @Override public int ingest() { return processPendingFragments(); } /**{@inheritDoc}*/ @Override public String getIngestType() { return "daybreak fragment"; } /**{@inheritDoc}*/ @Override public String getCollateType() { return "daybreak report"; } /**{@inheritDoc}*/ @Override public int collateDaybreakReports() { DaybreakReportSet<DaybreakFile> reportSet; try { reportSet = daybreakDao.getIncomingReports(); } catch (IOException ex){ logger.error("Could not retrieve incoming daybreak files"); return 0; } // Prints the status of all files found in the incoming directory if(!reportSet.getCompleteReports().isEmpty()) logger.info(" --- Complete reports ---"); for (DaybreakReport<DaybreakFile> daybreakReport: reportSet.getCompleteReports().values()) { logger.info("Report " + daybreakReport.getReportDate()); daybreakReport.getReportDocs().values().forEach(daybreakFile -> logger.info('\t' + daybreakFile.getFileName())); } if(!reportSet.getPartialReports().isEmpty()) logger.info(" --- Partial reports --- "); for (DaybreakReport<DaybreakFile> daybreakReport: reportSet.getPartialReports().values()) { logger.info("Report " + daybreakReport.getReportDate()); daybreakReport.getReportDocs().values().forEach(daybreakFile -> logger.info('\t' + daybreakFile.getFileName())); } if(!reportSet.getDuplicateDocuments().isEmpty()) logger.info(" --- Duplicate files --- "); reportSet.getDuplicateDocuments().forEach(file -> logger.info('\t' + file.getFileName())); // Collates all complete reports reportSet.getCompleteReports().values().forEach(this::collateDaybreakReport); return reportSet.getCompleteReports().values().size(); } /**{@inheritDoc} */ @Override public List<DaybreakFragment> getPendingDaybreakFragments() { return daybreakDao.getPendingDaybreakFragments(); } /**{@inheritDoc}*/ @Override public int processFragments(List<DaybreakFragment> fragments) { if (fragments.size() > 0) { ExecutorService executorService = Executors.newFixedThreadPool(4, threadFactory); logger.info("Processing " + fragments.size() + " daybreak fragments"); for (DaybreakFragment daybreakFragment : fragments) { executorService.submit(() -> processFragment(daybreakFragment)); } executorService.shutdown(); try { // Allow maximum of 30 minutes before un-blocking executorService.awaitTermination(30, TimeUnit.MINUTES); } catch (InterruptedException e) { e.printStackTrace(); } } return fragments.size(); } /**{@inheritDoc}*/ @Override public int processPendingFragments() { List<DaybreakFragment> daybreakFragments = getPendingDaybreakFragments(); int processedCount = processFragments(daybreakFragments); // Set associated reports as processed daybreakFragments.stream() .map(DaybreakFragment::getReportDate) .distinct() .forEach(daybreakDao::setProcessed); return processedCount; } /**{@inheritDoc}*/ @Override public void updatePendingProcessing(DaybreakBillId fragmentId, boolean pendingProcessing) { if(pendingProcessing){ daybreakDao.setPendingProcessing(fragmentId); } else{ daybreakDao.setProcessed(fragmentId); } } /** --- Helper methods --- */ /** * Goes through each file in a daybreak report, stores it, archives it and extracts * any daybreak fragments or page file entries from the file. * All fragments and entries are then stored * @param daybreakReport */ private void collateDaybreakReport(DaybreakReport<DaybreakFile> daybreakReport){ List<DaybreakFragment> daybreakFragments = new ArrayList<>(); List<PageFileEntry> pageFileEntries = new ArrayList<>(); logger.info("Collating " + daybreakReport.getReportDate()); for (DaybreakFile daybreakFile : daybreakReport.getReportDocs().values()) { // Add each file reference to the store daybreakDao.updateDaybreakFile(daybreakFile); logger.debug("Parsing " + daybreakFile.getFileName()); // Get daybreak fragments or page file entries from the daybreak file depending on the type try { if(daybreakFile.getDaybreakDocType()==DaybreakDocType.PAGE_FILE){ pageFileEntries.addAll(DaybreakPageFileParser.extractPageFileEntries(daybreakFile)); } else{ daybreakFragments.addAll(DaybreakFileParser.extractDaybreakFragments(daybreakFile)); } } catch(IOException ex){ logger.error("Could not parse daybreak file " + daybreakFile.getFileName()); } } // Add a new report entry daybreakDao.updateDaybreakReport(daybreakReport.getReportDate()); // Add all fragments and entries to the store logger.info("Saving daybreak fragments"); daybreakFragments.parallelStream().forEach(daybreakDao::updateDaybreakFragment); logger.info("Saving page file entries"); pageFileEntries.parallelStream().forEach(daybreakDao::updatePageFileEntry); // Archive the report files daybreakReport.getReportDocs().values().forEach(daybreakFile ->{ try { daybreakDao.archiveDaybreakFile(daybreakFile); } catch (IOException ex){ logger.error("An error occurred while archiving " + daybreakFile.getFileName()); } }); } /** * Processes an individual daybreak fragment and updates teh persistence layer * @param daybreakFragment */ private void processFragment(DaybreakFragment daybreakFragment){ // This method is called by the ExecutorService which does not pass exceptions to the calling code. // Log them here so we have visibility into any errors occurring. try { // Parse the fragment into a bill DaybreakBill daybreakBill = DaybreakFragmentParser.extractDaybreakBill(daybreakFragment); // Update the persistence layer daybreakDao.updateDaybreakBill(daybreakBill); // Set the fragment as processed daybreakDao.setProcessed(daybreakFragment.getDaybreakBillId()); } catch(Exception ex) { logger.error("An error has occurred while processing a daybreak fragment.", ex); } } }