package gov.nysenate.openleg.dao.calendar.alert;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import gov.nysenate.openleg.dao.base.*;
import gov.nysenate.openleg.dao.calendar.data.SqlCalendarDao;
import gov.nysenate.openleg.model.base.Version;
import gov.nysenate.openleg.model.bill.BillId;
import gov.nysenate.openleg.model.calendar.Calendar;
import gov.nysenate.openleg.model.calendar.*;
import gov.nysenate.openleg.model.calendar.alert.CalendarAlertFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static gov.nysenate.openleg.util.DateUtils.toDate;
/**
* Responsible for database access of calendar alert references. These are similar to Calendar objects
* but are based off LBDC Alert emails and used in the qa process.
*/
@Repository
public class SqlCalendarAlertDao extends SqlBaseDao implements CalendarAlertDao {
private static final Logger logger = LoggerFactory.getLogger(SqlCalendarDao.class);
public Calendar getCalendar(CalendarId calendarId) throws DataAccessException {
ImmutableParams calParams = ImmutableParams.from(getCalendarIdParams(calendarId));
Calendar calendar = jdbcNamed.queryForObject(SqlCalendarAlertQuery.SELECT_CALENDAR.getSql(schema()), calParams, CalendarRowMapper);
return calendar;
}
public List<CalendarId> getCalendarIds(int year, SortOrder calOrder, LimitOffset limitOffset) {
OrderBy orderBy = new OrderBy("calendar_no", calOrder);
ImmutableParams yearParam = ImmutableParams.from(new MapSqlParameterSource("year", year));
return jdbcNamed.query(SqlCalendarAlertQuery.SELECT_CALENDAR_IDS.getSql(schema(), orderBy, limitOffset),
yearParam, new CalendarIdRowMapper());
}
public void updateCalendar(Calendar calendar, CalendarAlertFile file) throws DataAccessException {
logger.trace("Updating calendar {} in database...", calendar);
ImmutableParams calParams = ImmutableParams.from(getCalendarParams(calendar, file));
// Update base calendar
if (jdbcNamed.update(SqlCalendarAlertQuery.UPDATE_CALENDAR.getSql(schema()), calParams) == 0) {
jdbcNamed.update(SqlCalendarAlertQuery.INSERT_CALENDAR.getSql(schema()), calParams);
}
// Update the associated calendar supplementals
updateCalSupplementals(calendar, file, calParams);
// Update the associated active lists
updateCalActiveLists(calendar, file, calParams);
}
public List<Calendar> getCalendarAlertsByDateRange(LocalDateTime start, LocalDateTime end) {
MapSqlParameterSource params = getDateRangeParams(start, end);
List<Calendar> calendars = jdbcNamed.queryForObject(SqlCalendarAlertQuery.SELECT_CALENDAR_RANGE.getSql(schema()), params, new CalendarListRowMapper());
return populateCalendars(calendars);
}
public void markAsChecked(CalendarId id) {
MapSqlParameterSource params = getCalendarIdParams(id);
params.addValue("checked", true);
jdbcNamed.update(SqlCalendarAlertQuery.MARK_CHECKED.getSql(schema()), params);
}
public void markProdAsChecked(CalendarId id) {
MapSqlParameterSource params = getCalendarIdParams(id);
params.addValue("prodChecked", true);
jdbcNamed.update(SqlCalendarAlertQuery.MARK_PROD_CHECKED.getSql(schema()), params);
}
public List<Calendar> getUnCheckedCalendarAlerts() {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("checked", false);
return jdbcNamed.query(SqlCalendarAlertQuery.SELECT_UNCHECKED.getSql(schema()), params, CalendarRowMapper);
}
public List<Calendar> getProdUnCheckedCalendarAlerts() {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("prodChecked", false);
return jdbcNamed.query(SqlCalendarAlertQuery.SELECT_PROD_UNCHECKED.getSql(schema()), params, CalendarRowMapper);
}
/** --- Internal Methods --- */
private List<Calendar> populateCalendars(List<Calendar> calendars) {
for (Calendar cal: calendars) {
ImmutableParams calParams = ImmutableParams.from(getCalendarIdParams(cal.getId()));
cal.setSupplementalMap(getCalSupplementals(calParams));
cal.setActiveListMap(getActiveListMap(calParams));
}
return calendars;
}
/**
* Retrieves all the supplementals for a particular calendar.
*/
private TreeMap<Version, CalendarSupplemental> getCalSupplementals(ImmutableParams calParams) {
CalendarSupRowHandler calendarSupRowHandler = new CalendarSupRowHandler();
jdbcNamed.query(SqlCalendarAlertQuery.SELECT_CALENDAR_SUPS.getSql(schema()), calParams, calendarSupRowHandler);
return calendarSupRowHandler.getCalendarSupplementals().stream()
.collect(Collectors.toMap(CalendarSupplemental::getVersion, Function.identity(), (a, b) -> b, TreeMap::new));
}
/**
* Updates the calendar supplementals. Entries belonging to supplementals that have not changed will not
* be affected.
*/
private void updateCalSupplementals(Calendar calendar, CalendarAlertFile file, ImmutableParams calParams) {
Map<Version, CalendarSupplemental> existingCalSupMap = getCalSupplementals(calParams);
// Get the difference between the existing and current supplemental mappings
MapDifference<Version, CalendarSupplemental> diff =
Maps.difference(existingCalSupMap, calendar.getSupplementalMap());
// Delete any supplementals that were not found in the current map or were different.
Set<Version> deleteSupVersions = Sets.union(diff.entriesDiffering().keySet(), diff.entriesOnlyOnLeft().keySet());
for (Version supVersion : deleteSupVersions) {
ImmutableParams calSupParams = calParams.add(new MapSqlParameterSource("supVersion", supVersion.getValue()));
jdbcNamed.update(SqlCalendarAlertQuery.DELETE_CALENDAR_SUP.getSql(schema()), calSupParams);
}
// Insert any new or differing supplementals
Set<Version> updateSupVersions = Sets.union(diff.entriesDiffering().keySet(), diff.entriesOnlyOnRight().keySet());
for (Version supVersion : updateSupVersions) {
CalendarSupplemental sup = calendar.getSupplemental(supVersion);
ImmutableParams supParams = ImmutableParams.from(getCalSupplementalParams(sup, file));
jdbcNamed.update(SqlCalendarAlertQuery.INSERT_CALENDAR_SUP.getSql(schema()), supParams);
// Insert the calendar entries
for (CalendarSupplementalEntry entry : sup.getSectionEntries().values()) {
ImmutableParams entryParams = ImmutableParams.from(getCalSupEntryParams(sup, entry, file));
jdbcNamed.update(SqlCalendarAlertQuery.INSERT_CALENDAR_SUP_ENTRY.getSql(schema()), entryParams);
}
}
}
/**
* Retrieve the active list mappings for a specific calendar.
*/
private TreeMap<Integer, CalendarActiveList> getActiveListMap(ImmutableParams calParams) {
ActiveListRowHandler activeListRowHandler = new ActiveListRowHandler();
jdbcNamed.query(SqlCalendarAlertQuery.SELECT_CALENDAR_ACTIVE_LISTS.getSql(schema()), calParams, activeListRowHandler);
return activeListRowHandler.getActiveLists().stream()
.collect(Collectors.toMap(CalendarActiveList::getSequenceNo, Function.identity(), (a,b) -> b, TreeMap::new));
}
/**
* Updates the calendar active lists. Entries belonging to active lists that have not changed will not
* be affected.
*/
private void updateCalActiveLists(Calendar calendar, CalendarAlertFile file, ImmutableParams calParams) {
Map<Integer, CalendarActiveList> existingActiveListMap = getActiveListMap(calParams);
// Get the difference between the existing and current active list mappings.
MapDifference<Integer, CalendarActiveList> diff =
Maps.difference(existingActiveListMap, calendar.getActiveListMap());
// Delete any active lists that were not found in the current map or were different.
Set<Integer> deleteActListSeqs = Sets.union(diff.entriesDiffering().keySet(), diff.entriesOnlyOnLeft().keySet());
for (Integer actListSeq : deleteActListSeqs) {
ImmutableParams activeListParams = calParams.add(new MapSqlParameterSource("sequenceNo", actListSeq));
jdbcNamed.update(SqlCalendarAlertQuery.DELETE_CALENDAR_ACTIVE_LIST.getSql(schema()), activeListParams);
}
// Insert any new or differing active lists
Set<Integer> updateActListSeqs = Sets.union(diff.entriesDiffering().keySet(), diff.entriesOnlyOnRight().keySet());
for (Integer actListSeq : updateActListSeqs) {
CalendarActiveList actList = calendar.getActiveList(actListSeq);
ImmutableParams actListParams = ImmutableParams.from(getCalActiveListParams(actList, file));
jdbcNamed.update(SqlCalendarAlertQuery.INSERT_CALENDAR_ACTIVE_LIST.getSql(schema()), actListParams);
// Insert the active list entries
for (CalendarEntry entry : actList.getEntries()) {
ImmutableParams entryParams = ImmutableParams.from(getCalActiveListEntryParams(actList, entry, file));
jdbcNamed.update(SqlCalendarAlertQuery.INSERT_CALENDAR_ACTIVE_LIST_ENTRY.getSql(schema()), entryParams);
}
}
}
/** --- Helper Classes --- */
private RowMapper<Calendar> CalendarRowMapper = (rs, rowNum) -> {
Calendar calendar = setCalendarIdFromResultSet(rs);
setModPubDatesFromResultSet(calendar, rs);
ImmutableParams calParams = ImmutableParams.from(getCalendarIdParams(calendar.getId()));
calendar.setSupplementalMap(getCalSupplementals(calParams));
calendar.setActiveListMap(getActiveListMap(calParams));
return calendar;
};
private class CalendarListRowMapper implements RowMapper<List<Calendar>> {
@Override
public List<Calendar> mapRow(ResultSet rs, int rowNum) throws SQLException {
List<Calendar> calendars = new ArrayList<>();
while(rs.next()) {
Calendar calendar = setCalendarIdFromResultSet(rs);
setModPubDatesFromResultSet(calendar, rs);
calendars.add(calendar);
}
return calendars;
}
}
private Calendar setCalendarIdFromResultSet(ResultSet rs) throws SQLException {
return new Calendar(new CalendarId(rs.getInt("calendar_no"), rs.getInt("calendar_year")));
}
private class CalendarIdRowMapper implements RowMapper<CalendarId>
{
@Override
public CalendarId mapRow(ResultSet rs, int rowNum) throws SQLException {
return new CalendarId(rs.getInt("calendar_no"), rs.getInt("calendar_year"));
}
}
private class CalendarSupRowMapper implements RowMapper<CalendarSupplemental>
{
@Override
public CalendarSupplemental mapRow(ResultSet rs, int rowNum) throws SQLException {
CalendarId calendarId = new CalendarId(rs.getInt("calendar_no"), rs.getInt("calendar_year"));
Version version = Version.of(rs.getString("sup_version"));
LocalDate calDate = getLocalDateFromRs(rs, "calendar_date");
LocalDateTime releaseDateTime = getLocalDateTimeFromRs(rs, "release_date_time");
CalendarSupplemental calSup = new CalendarSupplemental(calendarId, version, calDate, releaseDateTime);
setModPubDatesFromResultSet(calSup, rs);
return calSup;
}
}
private class CalendarSupEntryRowMapper implements RowMapper<CalendarSupplementalEntry>
{
@Override
public CalendarSupplementalEntry mapRow(ResultSet rs, int rowNum) throws SQLException {
CalendarSectionType sectionType = CalendarSectionType.valueOfCode(rs.getInt("section_code"));
int billCalNo = rs.getInt("bill_calendar_no");
BillId billId = new BillId(rs.getString("bill_print_no"), rs.getInt("bill_session_year"),
rs.getString("bill_amend_version"));
BillId subBillId = null;
if (rs.getString("sub_bill_print_no") != null) {
subBillId = new BillId(rs.getString("sub_bill_print_no"), rs.getInt("sub_bill_session_year"),
rs.getString("sub_bill_amend_version"));
}
boolean high = rs.getBoolean("high");
return new CalendarSupplementalEntry(billCalNo, sectionType, billId, subBillId, high);
}
}
private class CalendarSupRowHandler implements RowCallbackHandler
{
private CalendarSupRowMapper calendarSupRowMapper;
private CalendarSupEntryRowMapper calendarSupEntryRowMapper;
private Map<Integer, CalendarSupplemental> resultMap;
public CalendarSupRowHandler() {
calendarSupRowMapper = new CalendarSupRowMapper();
calendarSupEntryRowMapper = new CalendarSupEntryRowMapper();
resultMap = new LinkedHashMap<>();
}
@Override
public void processRow(ResultSet rs) throws SQLException {
Integer calSupId = rs.getInt("calendar_sup_id");
if (!resultMap.containsKey(calSupId)) {
resultMap.put(calSupId, calendarSupRowMapper.mapRow(rs, rs.getRow()));
}
resultMap.get(calSupId).addEntry(calendarSupEntryRowMapper.mapRow(rs, rs.getRow()));
}
public ArrayList<CalendarSupplemental> getCalendarSupplementals() {
return new ArrayList<>(resultMap.values());
}
}
private class CalendarActiveListRowMapper implements RowMapper<CalendarActiveList>
{
@Override
public CalendarActiveList mapRow(ResultSet rs, int rowNum) throws SQLException {
CalendarActiveList activeList = new CalendarActiveList();
activeList.setSequenceNo(rs.getInt("sequence_no"));
activeList.setCalendarId(new CalendarId(rs.getInt("calendar_no"), rs.getInt("calendar_year")));
activeList.setCalDate(getLocalDateFromRs(rs, "calendar_date"));
activeList.setReleaseDateTime(getLocalDateTimeFromRs(rs, "release_date_time"));
setModPubDatesFromResultSet(activeList, rs);
return activeList;
}
}
private class CalendarActiveListEntryRowMapper implements RowMapper<CalendarEntry>
{
@Override
public CalendarEntry mapRow(ResultSet rs, int rowNum) throws SQLException {
CalendarEntry entry = new CalendarEntry();
entry.setBillCalNo(rs.getInt("bill_calendar_no"));
entry.setBillId(new BillId(rs.getString("bill_print_no"), rs.getInt("bill_session_year"),
rs.getString("bill_amend_version")));
return entry;
}
}
private class ActiveListRowHandler implements RowCallbackHandler
{
private CalendarActiveListRowMapper calendarActiveListRowMapper;
private CalendarActiveListEntryRowMapper calendarActiveListEntryRowMapper;
private Map<Integer, CalendarActiveList> resultMap;
public ActiveListRowHandler() {
calendarActiveListRowMapper = new CalendarActiveListRowMapper();
calendarActiveListEntryRowMapper = new CalendarActiveListEntryRowMapper();
resultMap = new LinkedHashMap<>();
}
@Override
public void processRow(ResultSet rs) throws SQLException {
Integer calALId = rs.getInt("calendar_active_list_id");
if (!resultMap.containsKey(calALId)) {
resultMap.put(calALId, calendarActiveListRowMapper.mapRow(rs, rs.getRow()));
}
resultMap.get(calALId).addEntry(calendarActiveListEntryRowMapper.mapRow(rs, rs.getRow()));
}
public ArrayList<CalendarActiveList> getActiveLists() {
return new ArrayList<>(resultMap.values());
}
}
/** --- Param Source Methods --- */
private static MapSqlParameterSource getCalendarIdParams(CalendarId calendarId) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(calendarId, params);
return params;
}
private static MapSqlParameterSource getCalendarParams(Calendar calendar, CalendarAlertFile file) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(calendar.getId(), params);
addModPubDateParams(calendar.getModifiedDateTime(), calendar.getPublishedDateTime(), params);
addLastFile(file, params);
return params;
}
private MapSqlParameterSource getDateRangeParams(LocalDateTime start, LocalDateTime end) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("startTime", toDate(start));
params.addValue("endTime", toDate(end));
return params;
}
private static MapSqlParameterSource getCalSupplementalParams(CalendarSupplemental sup, CalendarAlertFile file) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(sup.getCalendarId(), params);
params.addValue("supVersion", sup.getVersion().getValue());
params.addValue("calendarDate", toDate(sup.getCalDate()));
params.addValue("releaseDateTime", toDate(sup.getReleaseDateTime()));
addModPubDateParams(sup.getModifiedDateTime(), sup.getPublishedDateTime(), params);
addLastFile(file, params);
return params;
}
private static MapSqlParameterSource getCalSupEntryParams(CalendarSupplemental sup, CalendarSupplementalEntry entry,
CalendarAlertFile file) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(sup.getCalendarId(), params);
params.addValue("supVersion", sup.getVersion().getValue());
params.addValue("sectionCode", entry.getSectionType().getCode());
params.addValue("billCalNo", entry.getBillCalNo());
addBillIdParams(entry.getBillId(), params);
BillId subBillId = entry.getSubBillId();
params.addValue("subPrintNo", (subBillId != null) ? subBillId.getBasePrintNo() : null);
params.addValue("subSession", (subBillId != null) ? subBillId.getSession().getYear() : null);
params.addValue("subAmendVersion", (subBillId != null) ? subBillId.getVersion().getValue() : null);
params.addValue("high", entry.getBillHigh());
addLastFile(file, params);
return params;
}
private static MapSqlParameterSource getCalActiveListParams(CalendarActiveList actList, CalendarAlertFile file) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(actList.getCalendarId(), params);
params.addValue("sequenceNo", actList.getSequenceNo());
params.addValue("calendarDate", toDate(actList.getCalDate()));
params.addValue("releaseDateTime", toDate(actList.getReleaseDateTime()));
params.addValue("notes", actList.getNotes());
addModPubDateParams(actList.getModifiedDateTime(), actList.getPublishedDateTime(), params);
addLastFile(file, params);
return params;
}
private static MapSqlParameterSource getCalActiveListEntryParams(CalendarActiveList actList,
CalendarEntry entry, CalendarAlertFile file) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(actList.getCalendarId(), params);
params.addValue("sequenceNo", actList.getSequenceNo());
params.addValue("billCalendarNo", entry.getBillCalNo());
addBillIdParams(entry.getBillId(), params);
addLastFile(file, params);
return params;
}
private static void addCalendarIdParams(CalendarId calendarId, MapSqlParameterSource params) {
params.addValue("calendarNo", calendarId.getCalNo());
params.addValue("year", calendarId.getYear());
}
private static void addBillIdParams(BillId billId, MapSqlParameterSource params) {
params.addValue("printNo", billId.getBasePrintNo());
params.addValue("session", billId.getSession().getYear());
params.addValue("amendVersion", billId.getVersion().getValue());
}
private static MapSqlParameterSource addLastFile(CalendarAlertFile file, MapSqlParameterSource params) {
return params.addValue("lastFile", file.getFile().getName());
}
}