package gov.nysenate.openleg.processor.base; import com.google.common.eventbus.EventBus; import gov.nysenate.openleg.model.agenda.Agenda; import gov.nysenate.openleg.model.agenda.AgendaId; import gov.nysenate.openleg.model.agenda.AgendaNotFoundEx; import gov.nysenate.openleg.config.Environment; import gov.nysenate.openleg.model.base.SessionYear; import gov.nysenate.openleg.model.bill.BaseBillId; import gov.nysenate.openleg.model.bill.Bill; import gov.nysenate.openleg.model.bill.BillAmendment; import gov.nysenate.openleg.model.bill.BillId; import gov.nysenate.openleg.model.calendar.Calendar; import gov.nysenate.openleg.model.calendar.CalendarId; import gov.nysenate.openleg.model.entity.Chamber; import gov.nysenate.openleg.model.entity.SessionMember; import gov.nysenate.openleg.model.law.LawFile; import gov.nysenate.openleg.model.process.DataProcessAction; import gov.nysenate.openleg.model.process.DataProcessUnit; import gov.nysenate.openleg.model.process.DataProcessUnitEvent; import gov.nysenate.openleg.model.sobi.SobiFragment; import gov.nysenate.openleg.service.agenda.data.AgendaDataService; import gov.nysenate.openleg.service.agenda.event.BulkAgendaUpdateEvent; import gov.nysenate.openleg.service.bill.data.BillDataService; import gov.nysenate.openleg.service.bill.data.BillNotFoundEx; import gov.nysenate.openleg.service.bill.data.VetoDataService; import gov.nysenate.openleg.service.bill.event.BulkBillUpdateEvent; import gov.nysenate.openleg.service.calendar.data.CalendarDataService; import gov.nysenate.openleg.service.calendar.data.CalendarNotFoundEx; import gov.nysenate.openleg.service.calendar.event.BulkCalendarUpdateEvent; import gov.nysenate.openleg.service.entity.committee.data.CommitteeDataService; import gov.nysenate.openleg.service.entity.member.data.MemberService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; /** * The AbstractDataProcessor class is intended to serve as a common base for all the * data processors and provides functionality to fetch and persist various entity types. * This is to allow different processors to be consistent in how they utilize various data * operations. */ public abstract class AbstractDataProcessor { private static final Logger logger = LoggerFactory.getLogger(AbstractDataProcessor.class); @Autowired protected Environment env; /** --- Data Services --- */ @Autowired protected AgendaDataService agendaDataService; @Autowired protected BillDataService billDataService; @Autowired protected CalendarDataService calendarDataService; @Autowired protected CommitteeDataService committeeDataService; @Autowired protected MemberService memberService; @Autowired protected VetoDataService vetoDataService; /** --- Events --- */ @Autowired protected EventBus eventBus; /** --- Ingest Caches --- */ @Resource(name = "agendaIngestCache") protected IngestCache<AgendaId, Agenda, SobiFragment> agendaIngestCache; @Resource(name = "billIngestCache") protected IngestCache<BaseBillId, Bill, SobiFragment> billIngestCache; @Resource(name = "calendarIngestCache") protected IngestCache<CalendarId, Calendar, SobiFragment> calendarIngestCache; public abstract void init(); public void initBase() { eventBus.register(this); } /** --- Common Methods --- */ protected DataProcessUnit createProcessUnit(SobiFragment sobiFragment) { return new DataProcessUnit("SOBI-" + sobiFragment.getType().name(), sobiFragment.getFragmentId(), LocalDateTime.now(), DataProcessAction.INGEST); } protected DataProcessUnit createDataProcessUnit(LawFile lawFile) { return new DataProcessUnit("LAW_FILE", lawFile.getFileName(), LocalDateTime.now(), DataProcessAction.INGEST); } protected void postDataUnitEvent(DataProcessUnit unit) { unit.setEndDateTime(LocalDateTime.now()); eventBus.post(new DataProcessUnitEvent(unit)); } /** --- Bill Methods --- */ /** * Retrieves the base Bill container using the given billId from either the cache or the service layer. * If this base bill does not exist, it will be created. The amendment instance will also be created * if it does not exist. * * @param publishDate Date - Typically the date of the source data file. Only used when bill information * does not already exist and must be created. * @param billId BillId - The BillId to find a matching Bill for. * @return Bill */ protected final Bill getOrCreateBaseBill(LocalDateTime publishDate, BillId billId, SobiFragment fragment) { boolean isBaseVersion = BillId.isBaseVersion(billId.getVersion()); BaseBillId baseBillId = BillId.getBaseId(billId); Bill baseBill; // Check the cache, or hit the data service otherwise if (billIngestCache.has(baseBillId)) { baseBill = billIngestCache.get(baseBillId).getLeft(); } else { try { baseBill = billDataService.getBill(baseBillId); } catch (BillNotFoundEx ex) { // Create the bill since it does not exist and add it to the ingest cache. if (!isBaseVersion) { logger.warn("Bill Amendment {} filed without initial bill.", billId); } baseBill = new Bill(baseBillId); baseBill.setModifiedDateTime(publishDate); baseBill.setPublishedDateTime(publishDate); billIngestCache.set(baseBillId, baseBill, fragment); } billIngestCache.set(baseBillId, baseBill, fragment); } if (!baseBill.hasAmendment(billId.getVersion())) { BillAmendment billAmendment = new BillAmendment(baseBillId, billId.getVersion()); // If an active amendment exists, apply its ACT TO clause to this amendment if (baseBill.hasActiveAmendment()) { billAmendment.setActClause(baseBill.getActiveAmendment().getActClause()); } // Create the base version if an amendment was received before the base version if (!isBaseVersion) { if (!baseBill.hasAmendment(BillId.DEFAULT_VERSION)) { BillAmendment baseAmendment = new BillAmendment(baseBillId, BillId.DEFAULT_VERSION); baseBill.addAmendment(baseAmendment); baseBill.setActiveVersion(BillId.DEFAULT_VERSION); } // If the active amendment does not exist, create it if (!baseBill.hasActiveAmendment()) { BillAmendment activeAmendment = new BillAmendment(baseBillId, baseBill.getActiveVersion()); baseBill.addAmendment(activeAmendment); } // Otherwise pull 'initially shared' data from the currently active amendment else { BillAmendment activeAmendment = baseBill.getActiveAmendment(); billAmendment.setCoSponsors(activeAmendment.getCoSponsors()); billAmendment.setMultiSponsors(activeAmendment.getMultiSponsors()); } } logger.trace("Adding bill amendment: " + billAmendment); baseBill.addAmendment(billAmendment); } return baseBill; } /** * Flushes all bills stored in the cache to the persistence layer and clears the cache. */ protected void flushBillUpdates() { if (billIngestCache.getSize() > 0) { logger.info("Flushing {} bills", billIngestCache.getSize()); billIngestCache.getCurrentCache().forEach(entry -> billDataService.saveBill(entry.getLeft(), entry.getRight(), false)); logger.debug("Broadcasting bill updates..."); List<Bill> bills = billIngestCache.getCurrentCache().stream().map(entry -> entry.getLeft()).collect(Collectors.toList()); eventBus.post(new BulkBillUpdateEvent(bills, LocalDateTime.now())); billIngestCache.clearCache(); } } /** --- Member Methods --- */ /** * Retrieves a member from the LBDC short name. Creates a new unverified session member entry if no member can be retrieved. */ protected SessionMember getMemberFromShortName(String shortName, SessionYear sessionYear, Chamber chamber) throws ParseError { return memberService.getMemberByShortNameEnsured(shortName, sessionYear, chamber); } /** --- Agenda Methods --- */ /** * Retrieve an Agenda instance from the cache/backing store or create it if it does not exist. * * @param agendaId AgendaId - Retrieve Agenda via this agendaId. * @param fragment SobiFragment * @return Agenda */ protected final Agenda getOrCreateAgenda(AgendaId agendaId, SobiFragment fragment) { Agenda agenda; try { if (agendaIngestCache.has(agendaId)) { return agendaIngestCache.get(agendaId).getLeft(); } else { agenda = agendaDataService.getAgenda(agendaId); } } catch (AgendaNotFoundEx ex) { agenda = new Agenda(agendaId); agenda.setModifiedDateTime(fragment.getPublishedDateTime()); agenda.setPublishedDateTime(fragment.getPublishedDateTime()); } agendaIngestCache.set(agendaId, agenda, fragment); return agenda; } /** * Flushes all agendas stored in the cache to the persistence layer and clears the cache. */ protected void flushAgendaUpdates() { if (agendaIngestCache.getSize() > 0) { logger.info("Flushing {} agendas", agendaIngestCache.getSize()); agendaIngestCache.getCurrentCache().forEach( entry -> agendaDataService.saveAgenda(entry.getLeft(), entry.getRight(), false)); List<Agenda> agendas = agendaIngestCache.getCurrentCache().stream().map(entry -> entry.getLeft()).collect(Collectors.toList()); eventBus.post(new BulkAgendaUpdateEvent(agendas, LocalDateTime.now())); agendaIngestCache.clearCache(); } } /** --- Calendar Methods --- */ /** * Retrieve a Calendar from the cache/backing store or create it if it does not exist. * * @param calendarId CalendarId - Retrieve Calendar via this calendarId. * @param fragment SobiFragment * @return Calendar */ protected final Calendar getOrCreateCalendar(CalendarId calendarId, SobiFragment fragment) { Calendar calendar; try { if (calendarIngestCache.has(calendarId)) { return calendarIngestCache.get(calendarId).getLeft(); } else { calendar = calendarDataService.getCalendar(calendarId); } } catch (CalendarNotFoundEx ex) { calendar = new Calendar(calendarId); calendar.setModifiedDateTime(fragment.getPublishedDateTime()); calendar.setPublishedDateTime(fragment.getPublishedDateTime()); } calendarIngestCache.set(calendarId, calendar, fragment); return calendar; } /** * Flushes all calendars stored in the cache to the persistence layer and clears the cache. */ protected void flushCalendarUpdates() { if (calendarIngestCache.getSize() > 0) { logger.info("Flushing {} calendars", calendarIngestCache.getSize()); calendarIngestCache.getCurrentCache().forEach( entry -> calendarDataService.saveCalendar(entry.getLeft(), entry.getRight(), false)); List<Calendar> calendars = calendarIngestCache.getCurrentCache().stream().map(entry -> entry.getLeft()).collect(Collectors.toList()); eventBus.post(new BulkCalendarUpdateEvent(calendars, LocalDateTime.now())); calendarIngestCache.clearCache(); } } /** * Flushes all updates. */ protected void flushAllUpdates() { flushBillUpdates(); flushAgendaUpdates(); flushCalendarUpdates(); } }