package gov.nysenate.openleg.processor.agenda;
import gov.nysenate.openleg.model.agenda.*;
import gov.nysenate.openleg.model.base.Version;
import gov.nysenate.openleg.model.bill.BillId;
import gov.nysenate.openleg.model.entity.Chamber;
import gov.nysenate.openleg.model.entity.CommitteeId;
import gov.nysenate.openleg.model.process.DataProcessUnit;
import gov.nysenate.openleg.model.sobi.SobiFragment;
import gov.nysenate.openleg.model.sobi.SobiFragmentType;
import gov.nysenate.openleg.processor.base.AbstractDataProcessor;
import gov.nysenate.openleg.processor.sobi.SobiProcessor;
import gov.nysenate.openleg.util.DateUtils;
import gov.nysenate.openleg.util.XmlHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.annotation.PostConstruct;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Service
public class AgendaProcessor extends AbstractDataProcessor implements SobiProcessor
{
private static final Logger logger = LoggerFactory.getLogger(AgendaProcessor.class);
@Autowired private XmlHelper xml;
@PostConstruct
public void init() {
initBase();
}
/** {@inheritDoc} */
@Override
public SobiFragmentType getSupportedType() {
return SobiFragmentType.AGENDA;
}
/** {@inheritDoc} */
@Override
public void process(SobiFragment sobiFragment) {
logger.info("Processing Agenda...");
LocalDateTime modifiedDate = sobiFragment.getPublishedDateTime();
DataProcessUnit unit = createProcessUnit(sobiFragment);
try {
Document doc = xml.parse(sobiFragment.getText());
Node xmlAgenda = xml.getNode("SENATEDATA/senagenda", doc);
Integer agendaNo = xml.getInteger("@no", xmlAgenda);
Integer year = xml.getInteger("@year", xmlAgenda);
AgendaId agendaId = new AgendaId(agendaNo, year);
String action = xml.getString("@action", xmlAgenda);
// Remove the Agenda if the action = 'remove'
if (action.equalsIgnoreCase("remove")) {
logger.info("Removing {}", agendaId);
agendaDataService.deleteAgenda(agendaId);
}
// Otherwise update/insert any associated addenda.
else if (action.equalsIgnoreCase("replace")) {
Agenda agenda = getOrCreateAgenda(agendaId, sobiFragment);
agenda.setModifiedDateTime(modifiedDate);
NodeList xmlAddendums = xml.getNodeList("addendum", xmlAgenda);
// The initial state as well as updates to an Agenda are sent via addenda which
// have an id associated with them.
for (int i = 0; i < xmlAddendums.getLength(); i++) {
Node xmlAddendum = xmlAddendums.item(i);
String addendumId = xml.getString("@id", xmlAddendum);
logger.info("Updating Addendum {} for {}", addendumId, agenda);
LocalDate weekOf = DateUtils.getLrsLocalDate(xml.getString("weekof/text()", xmlAddendum));
LocalDateTime pubDateTime = DateUtils.getLrsDateTime(xml.getString("pubdate/text()", xmlAddendum) +
xml.getString("pubtime/text()", xmlAddendum));
AgendaInfoAddendum addendum = new AgendaInfoAddendum(agendaId, addendumId, weekOf, pubDateTime);
// Each addendum will contain a section for each committee that has an update.
NodeList xmlCommittees = xml.getNodeList("committees/committee", xmlAddendum);
for (int j = 0; j < xmlCommittees.getLength(); j++) {
Node xmlCommittee = xmlCommittees.item(j);
String name = xml.getString("name/text()", xmlCommittee);
// We only get agendas for senate committees. This may or may not change in the future.
CommitteeId committeeId = new CommitteeId(Chamber.SENATE, name);
String chair = xml.getString("chair/text()", xmlCommittee);
String location = xml.getString("location/text()", xmlCommittee);
// The notes are very important because they will contain any vital information such
// as if the meeting is off the floor (ad-hoc) or if there was a change to the meeting time.
String notes = xml.getString("notes/text()", xmlCommittee);
// The meeting date/time may not be entirely accurate because often the data is expressed
// through the notes field, especially for multiple ad-hoc meetings during the end of session.
LocalDateTime meetDateTime = DateUtils.getLrsDateTime(
xml.getString("meetdate/text()", xmlCommittee) + xml.getString("meettime/text()", xmlCommittee));
// Construct the committee info model using the parsed data.
AgendaInfoCommittee infoCommittee =
new AgendaInfoCommittee(committeeId, agendaId, Version.of(addendumId), chair, location, notes, meetDateTime);
// A committee will have a collection of bills that are up for consideration.
NodeList xmlBills = xml.getNodeList("bills/bill", xmlCommittee);
for (int k = 0; k < xmlBills.getLength(); k++) {
Node xmlBill = xmlBills.item(k);
String printNo = xml.getString("@no", xmlBill);
String message = xml.getString("message/text()", xmlBill);
BillId billId = new BillId(printNo, DateUtils.resolveSession(year));
AgendaInfoCommitteeItem item = new AgendaInfoCommitteeItem(billId, message);
infoCommittee.addCommitteeItem(item);
}
addendum.putCommittee(infoCommittee);
}
agenda.putAgendaInfoAddendum(addendum);
}
}
}
catch (IOException | SAXException | XPathExpressionException ex) {
logger.error("Failed to parse agenda fragment {}", sobiFragment.getFragmentId(), ex);
unit.addException("Failed to parse Agenda: " + ex.getMessage());
}
// Notify the data processor that an agenda fragment has finished processing
postDataUnitEvent(unit);
if (!env.isSobiBatchEnabled() || agendaIngestCache.exceedsCapacity()) {
flushAllUpdates(); // Flush all the things
}
}
@Override
public void postProcess() {
flushAllUpdates();
}
}