package gov.nysenate.openleg.dao.calendar.data;
import com.google.common.collect.*;
import gov.nysenate.openleg.dao.base.*;
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.sobi.SobiFragment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
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;
@Repository
public class SqlCalendarDao extends SqlBaseDao implements CalendarDao
{
private static final Logger logger = LoggerFactory.getLogger(SqlCalendarDao.class);
/** {@inheritDoc} */
@Override
public Calendar getCalendar(CalendarId calendarId) throws DataAccessException {
ImmutableParams calParams = ImmutableParams.from(getCalendarIdParams(calendarId));
// Get the base calendar
Calendar calendar = jdbcNamed.queryForObject(SqlCalendarQuery.SELECT_CALENDAR.getSql(schema()), calParams, new CalendarRowMapper());
// Get the supplementals
calendar.setSupplementalMap(getCalSupplementals(calParams));
// Get the active lists
calendar.setActiveListMap(getActiveListMap(calParams));
// Calendar is fully constructed
return calendar;
}
/** {@inheritDoc} */
@Override
public CalendarActiveList getActiveList(CalendarActiveListId calendarActiveListId) throws DataAccessException {
ImmutableParams params = ImmutableParams.from(getCalendarActiveListIdParams(calendarActiveListId));
ActiveListRowHandler activeListRowHandler = new ActiveListRowHandler();
jdbcNamed.query(SqlCalendarQuery.SELECT_CALENDAR_ACTIVE_LIST.getSql(schema()), params, activeListRowHandler);
try {
return activeListRowHandler.getActiveLists().get(0);
}
catch (IndexOutOfBoundsException ex) {
throw new EmptyResultDataAccessException(1);
}
}
/** {@inheritDoc} */
@Override
public CalendarSupplemental getCalendarSupplemental(CalendarSupplementalId calendarSupplementalId) throws DataAccessException {
ImmutableParams params = ImmutableParams.from(getCalendarSupplementalIdParams(calendarSupplementalId));
CalendarSupRowHandler calendarSupRowHandler = new CalendarSupRowHandler();
jdbcNamed.query(SqlCalendarQuery.SELECT_CALENDAR_SUP.getSql(schema()), params, calendarSupRowHandler);
try {
return calendarSupRowHandler.getCalendarSupplementals().get(0);
}
catch (IndexOutOfBoundsException ex) {
throw new EmptyResultDataAccessException(1);
}
}
/** {@inheritDoc} */
@Override
public Range<Integer> getActiveYearRange() {
if (getCalendarCount() == 0) {
throw new EmptyResultDataAccessException("Cannot retrieve active year range as there are no stored calendars", 1);
}
return jdbc.queryForObject(SqlCalendarQuery.SELECT_CALENDAR_YEAR_RANGE.getSql(schema()),
(rs, row) -> Range.closed(rs.getInt("min"), rs.getInt("max")));
}
/** {@inheritDoc} */
@Override
public int getCalendarCount() {
return jdbc.queryForObject(SqlCalendarQuery.SELECT_TOTAL_COUNT.getSql(schema()), Integer.class);
}
/** {@inheritDoc} */
@Override
public int getCalendarCount(int year) {
ImmutableParams yearParam = ImmutableParams.from(new MapSqlParameterSource("year", year));
return jdbcNamed.queryForObject(SqlCalendarQuery.SELECT_CALENDARS_COUNT.getSql(schema()), yearParam, Integer.class);
}
/** {@inheritDoc} */
@Override
public int getActiveListCount(int year) {
ImmutableParams yearParam = ImmutableParams.from(new MapSqlParameterSource("year", year));
return jdbcNamed.queryForObject(SqlCalendarQuery.SELECT_CALENDAR_ACTIVE_LIST_ID_COUNT.getSql(schema()), yearParam, Integer.class);
}
/** {@inheritDoc} */
@Override
public int getCalendarSupplementalCount(int year) {
ImmutableParams yearParam = ImmutableParams.from(new MapSqlParameterSource("year", year));
return jdbcNamed.queryForObject(SqlCalendarQuery.SELECT_CALENDAR_SUP_ID_COUNT.getSql(schema()), yearParam, Integer.class);
}
/** {@inheritDoc} */
@Override
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(SqlCalendarQuery.SELECT_CALENDAR_IDS.getSql(schema(), orderBy, limitOffset),
yearParam, new CalendarIdRowMapper());
}
/** {@inheritDoc} */
@Override
public List<CalendarActiveListId> getActiveListIds(int year, SortOrder sortOrder, LimitOffset limitOffset)
throws DataAccessException {
OrderBy orderBy = new OrderBy("calendar_no", sortOrder, "sequence_no", sortOrder);
ImmutableParams yearParam = ImmutableParams.from(new MapSqlParameterSource("year", year));
return jdbcNamed.query(SqlCalendarQuery.SELECT_CALENDAR_ACTIVE_LIST_IDS.getSql(schema(), orderBy, limitOffset),
yearParam, new CalendarActiveListIdRowMapper());
}
/** {@inheritDoc} */
@Override
public List<CalendarSupplementalId> getCalendarSupplementalIds(int year, SortOrder sortOrder, LimitOffset limitOffset)
throws DataAccessException {
OrderBy orderBy = new OrderBy("calendar_no", sortOrder, "sup_version", sortOrder);
ImmutableParams yearParam = ImmutableParams.from(new MapSqlParameterSource("year", year));
return jdbcNamed.query(SqlCalendarQuery.SELECT_CALENDAR_SUP_IDS.getSql(schema(), orderBy, limitOffset),
yearParam, new CalendarSupIdRowMapper());
}
/** {@inheritDoc} */
@Override
public void updateCalendar(Calendar calendar, SobiFragment fragment) throws DataAccessException {
logger.trace("Updating calendar {} in database...", calendar);
ImmutableParams calParams = ImmutableParams.from(getCalendarParams(calendar, fragment));
// Update base calendar
if (jdbcNamed.update(SqlCalendarQuery.UPDATE_CALENDAR.getSql(schema()), calParams) == 0) {
jdbcNamed.update(SqlCalendarQuery.INSERT_CALENDAR.getSql(schema()), calParams);
}
// Update the associated calendar supplementals
updateCalSupplementals(calendar, fragment, calParams);
// Update the associated active lists
updateCalActiveLists(calendar, fragment, calParams);
}
/** --- Internal Methods --- */
/**
* Retrieves all the supplementals for a particular calendar.
*/
private TreeMap<Version, CalendarSupplemental> getCalSupplementals(ImmutableParams calParams) {
CalendarSupRowHandler calendarSupRowHandler = new CalendarSupRowHandler();
jdbcNamed.query(SqlCalendarQuery.SELECT_CALENDAR_SUPS.getSql(schema()), calParams, calendarSupRowHandler);
return calendarSupRowHandler.getCalendarSupplementals().stream()
.collect(Collectors.toMap(CalendarSupplemental::getVersion, Function.identity(), (a,b) -> b, TreeMap::new));
}
/**
* Retrieves the supplemental entries for a particular supplemental.
*/
private LinkedListMultimap<CalendarSectionType, CalendarSupplementalEntry> getCalSupEntries(ImmutableParams supParams) {
List<CalendarSupplementalEntry> entries =
jdbcNamed.query(SqlCalendarQuery.SELECT_CALENDAR_SUP_ENTRIES.getSql(schema()), supParams, new CalendarSupEntryRowMapper());
LinkedListMultimap<CalendarSectionType, CalendarSupplementalEntry> sectionEntries = LinkedListMultimap.create();
for (CalendarSupplementalEntry entry : entries) {
sectionEntries.put(entry.getSectionType(), entry);
}
return sectionEntries;
}
/**
* Updates the calendar supplementals. Entries belonging to supplementals that have not changed will not
* be affected.
*/
private void updateCalSupplementals(Calendar calendar, SobiFragment fragment, 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(SqlCalendarQuery.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, fragment));
jdbcNamed.update(SqlCalendarQuery.INSERT_CALENDAR_SUP.getSql(schema()), supParams);
// Insert the calendar entries
for (CalendarSupplementalEntry entry : sup.getSectionEntries().values()) {
ImmutableParams entryParams = ImmutableParams.from(getCalSupEntryParams(sup, entry, fragment));
jdbcNamed.update(SqlCalendarQuery.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(SqlCalendarQuery.SELECT_CALENDAR_ACTIVE_LISTS.getSql(schema()), calParams, activeListRowHandler);
return activeListRowHandler.getActiveLists().stream()
.collect(Collectors.toMap(CalendarActiveList::getSequenceNo, Function.identity(), (a,b) -> b, TreeMap::new));
}
/**
* Retrieve entries for a specific active list.
*/
private List<CalendarEntry> getActiveListEntries(ImmutableParams activeListParams) {
return jdbcNamed.query(SqlCalendarQuery.SELECT_CALENDAR_ACTIVE_LIST_ENTRIES.getSql(schema()), activeListParams,
new CalendarActiveListEntryRowMapper());
}
/**
* Updates the calendar active lists. Entries belonging to active lists that have not changed will not
* be affected.
*/
private void updateCalActiveLists(Calendar calendar, SobiFragment fragment, 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(SqlCalendarQuery.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, fragment));
jdbcNamed.update(SqlCalendarQuery.INSERT_CALENDAR_ACTIVE_LIST.getSql(schema()), actListParams);
// Insert the active list entries
for (CalendarEntry entry : actList.getEntries()) {
ImmutableParams entryParams = ImmutableParams.from(getCalActiveListEntryParams(actList, entry, fragment));
jdbcNamed.update(SqlCalendarQuery.INSERT_CALENDAR_ACTIVE_LIST_ENTRY.getSql(schema()), entryParams);
}
}
}
/** --- Helper Classes --- */
protected class CalendarRowMapper implements RowMapper<Calendar>
{
@Override
public Calendar mapRow(ResultSet rs, int rowNum) throws SQLException {
Calendar calendar = new Calendar(new CalendarId(rs.getInt("calendar_no"), rs.getInt("calendar_year")));
setModPubDatesFromResultSet(calendar, rs);
return calendar;
}
}
protected 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"));
}
}
protected 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;
}
}
protected class CalendarSupIdRowMapper implements RowMapper<CalendarSupplementalId> {
@Override
public CalendarSupplementalId mapRow(ResultSet rs, int rowNum) throws SQLException {
return new CalendarSupplementalId(rs.getInt("calendar_no"), rs.getInt("calendar_year"), Version.of(rs.getString("sup_version")));
}
}
protected 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);
}
}
protected class CalendarSupRowHandler implements RowCallbackHandler
{
protected CalendarSupRowMapper calendarSupRowMapper;
protected CalendarSupEntryRowMapper calendarSupEntryRowMapper;
protected 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("sup_id");
if (!resultMap.containsKey(calSupId)) {
resultMap.put(calSupId, calendarSupRowMapper.mapRow(rs, rs.getRow()));
}
Integer supEntryId = rs.getInt("ent_id");
if (supEntryId > 0) {
resultMap.get(calSupId).addEntry(calendarSupEntryRowMapper.mapRow(rs, rs.getRow()));
}
}
public ArrayList<CalendarSupplemental> getCalendarSupplementals() {
return new ArrayList<>(resultMap.values());
}
}
protected 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;
}
}
protected class CalendarActiveListIdRowMapper implements RowMapper<CalendarActiveListId>
{
@Override
public CalendarActiveListId mapRow(ResultSet rs, int rowNum) throws SQLException {
return new CalendarActiveListId(rs.getInt("calendar_no"), rs.getInt("calendar_year"), rs.getInt("sequence_no"));
}
}
protected 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;
}
}
protected class ActiveListRowHandler implements RowCallbackHandler
{
protected CalendarActiveListRowMapper calendarActiveListRowMapper;
protected CalendarActiveListEntryRowMapper calendarActiveListEntryRowMapper;
protected 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("al_id");
if (!resultMap.containsKey(calALId)) {
resultMap.put(calALId, calendarActiveListRowMapper.mapRow(rs, rs.getRow()));
}
Integer entId = rs.getInt("ent_id");
if (entId > 0) {
resultMap.get(calALId).addEntry(calendarActiveListEntryRowMapper.mapRow(rs, rs.getRow()));
}
}
public ArrayList<CalendarActiveList> getActiveLists() {
return new ArrayList<>(resultMap.values());
}
}
/** --- Param Source Methods --- */
protected static MapSqlParameterSource getCalendarIdParams(CalendarId calendarId) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(calendarId, params);
return params;
}
protected static MapSqlParameterSource getCalendarActiveListIdParams(CalendarActiveListId calendarActiveListId) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(calendarActiveListId, params);
params.addValue("sequenceNo", calendarActiveListId.getSequenceNo());
return params;
}
protected static MapSqlParameterSource getCalendarSupplementalIdParams(CalendarSupplementalId calendarSupplementalId) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(calendarSupplementalId, params);
params.addValue("supVersion", calendarSupplementalId.getVersion().getValue());
return params;
}
protected static MapSqlParameterSource getCalendarParams(Calendar calendar, SobiFragment fragment) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(calendar.getId(), params);
addModPubDateParams(calendar.getModifiedDateTime(), calendar.getPublishedDateTime(), params);
addLastFragmentParam(fragment, params);
return params;
}
protected static MapSqlParameterSource getCalSupplementalParams(CalendarSupplemental sup, SobiFragment fragment) {
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);
addLastFragmentParam(fragment, params);
return params;
}
protected static MapSqlParameterSource getCalSupEntryParams(CalendarSupplemental sup, CalendarSupplementalEntry entry,
SobiFragment fragment) {
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());
addLastFragmentParam(fragment, params);
return params;
}
protected static MapSqlParameterSource getCalActiveListParams(CalendarActiveList actList, SobiFragment fragment) {
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);
addLastFragmentParam(fragment, params);
return params;
}
protected static MapSqlParameterSource getCalActiveListEntryParams(CalendarActiveList actList,
CalendarEntry entry, SobiFragment fragment) {
MapSqlParameterSource params = new MapSqlParameterSource();
addCalendarIdParams(actList.getCalendarId(), params);
params.addValue("sequenceNo", actList.getSequenceNo());
params.addValue("billCalendarNo", entry.getBillCalNo());
addBillIdParams(entry.getBillId(), params);
addLastFragmentParam(fragment, params);
return params;
}
protected static void addCalendarIdParams(CalendarId calendarId, MapSqlParameterSource params) {
params.addValue("calendarNo", calendarId.getCalNo());
params.addValue("year", calendarId.getYear());
}
protected static void addBillIdParams(BillId billId, MapSqlParameterSource params) {
params.addValue("printNo", billId.getBasePrintNo());
params.addValue("session", billId.getSession().getYear());
params.addValue("amendVersion", billId.getVersion().getValue());
}
}