package gov.nysenate.openleg.dao.law.data; import com.google.common.collect.Maps; import gov.nysenate.openleg.dao.base.*; import gov.nysenate.openleg.model.law.*; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataRetrievalFailureException; 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.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static gov.nysenate.openleg.dao.law.data.SqlLawDataQuery.*; import static gov.nysenate.openleg.util.DateUtils.toDate; @Repository public class SqlLawDataDao extends SqlBaseDao implements LawDataDao { private static final Logger logger = LoggerFactory.getLogger(SqlLawDataDao.class); /** {@inheritDoc} */ @Override public LawInfo getLawInfo(String lawId) throws DataAccessException { ImmutableParams lawIdParam = ImmutableParams.from(new MapSqlParameterSource("lawId", lawId)); return jdbcNamed.queryForObject(SqlLawDataQuery.SELECT_LAW_INFO_BY_ID.getSql(schema()), lawIdParam, lawInfoRowMapper); } /** {@inheritDoc} */ @Override public List<LawInfo> getLawInfos() { return jdbcNamed.query(SqlLawDataQuery.SELECT_LAW_INFO.getSql(schema()), lawInfoRowMapper); } @Override public Map<String, LocalDate> getLastPublishedMap() { List<Pair<String, LocalDate>> res = jdbcNamed.query(SqlLawDataQuery.SELECT_MAX_PUB_DATE.getSql(schema()), (rs, rowNum) -> { return Pair.of(rs.getString("law_id"), getLocalDateFromRs(rs, "max_pub_date")); }); return res.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue)); } /** {@inheritDoc} */ @Override public LawTree getLawTree(String lawId, LocalDate endPublishDate) throws DataAccessException { ImmutableParams treeParams = ImmutableParams.from(new MapSqlParameterSource() .addValue("lawId", lawId) .addValue("endPublishedDate", toDate(endPublishDate))); OrderBy orderBy = new OrderBy("sequence_no", SortOrder.ASC); // Retrieve the law info first LawInfo lawInfo = getLawInfo(lawId); // Handle the tree retrieval LawTreeRowCallbackHandler lawTreeHandler = new LawTreeRowCallbackHandler(lawInfo); jdbcNamed.query(SqlLawDataQuery.SELECT_LAW_TREE.getSql(schema(), orderBy, LimitOffset.ALL), treeParams, lawTreeHandler); LawTree lawTree = lawTreeHandler.getLawTree(); // Set all available published dates using a separate query lawTree.setPublishedDates(jdbcNamed.query(SqlLawDataQuery.SELECT_ALL_PUB_DATES.getSql( schema(), new OrderBy("published_date", SortOrder.ASC), LimitOffset.ALL), treeParams, (rs, rowNum) -> getLocalDateFromRs(rs, "published_date"))); return lawTree; } /** {@inheritDoc} */ @Override public LawDocument getLawDocument(String documentId, LocalDate endPublishDate) { ImmutableParams lawDocParams = ImmutableParams.from(new MapSqlParameterSource() .addValue("docId", documentId) .addValue("endPublishedDate", toDate(endPublishDate))); return jdbcNamed.queryForObject(SqlLawDataQuery.SELECT_LAW_DOCUMENT.getSql(schema()), lawDocParams, lawDocRowMapper); } /** {@inheritDoc} */ @Override public Map<String, LawDocument> getLawDocuments(String lawId, LocalDate endPublishDate) throws DataAccessException { ImmutableParams lawDocParams = ImmutableParams.from(new MapSqlParameterSource() .addValue("lawId", lawId) .addValue("endPublishedDate", toDate(endPublishDate))); List<LawDocument> docs = jdbcNamed.query(SqlLawDataQuery.SELECT_ALL_LAW_DOCUMENTS.getSql(schema()), lawDocParams, lawDocRowMapper); return Maps.uniqueIndex(docs, LawDocument::getDocumentId); } /** {@inheritDoc} */ @Override public void updateLawDocument(LawFile lawFile, LawDocument lawDocument) { ImmutableParams lawDocParams = ImmutableParams.from(getLawDocumentParams(lawFile, lawDocument)); if (jdbcNamed.update(SqlLawDataQuery.UPDATE_LAW_DOCUMENT.getSql(schema()), lawDocParams) == 0) { jdbcNamed.update(SqlLawDataQuery.INSERT_LAW_DOCUMENT.getSql(schema()), lawDocParams); } } /** {@inheritDoc} */ @Override public void updateLawTree(LawFile lawFile, LawTree lawTree) { ImmutableParams lawInfoParams = ImmutableParams.from(getLawInfoParams(lawTree.getLawInfo())); // Update the law info or insert it if (jdbcNamed.update(SqlLawDataQuery.UPDATE_LAW_INFO.getSql(schema()), lawInfoParams) == 0) { jdbcNamed.update(SqlLawDataQuery.INSERT_LAW_INFO.getSql(schema()), lawInfoParams); } ImmutableParams treeIdParams = ImmutableParams.from(getLawTreeParams(lawTree)); // Delete the existing tree if it exists jdbcNamed.update(SqlLawDataQuery.DELETE_TREE.getSql(schema()), treeIdParams); // Insert all the nodes in the tree lawTree.getRootNode().getAllNodes().forEach(n -> { ImmutableParams treeNodeParams = ImmutableParams.from(getLawTreeNodeParams(lawFile, lawTree, n)); jdbcNamed.update(SqlLawDataQuery.INSERT_LAW_TREE.getSql(schema()), treeNodeParams); }); } /** * Constructs a LawTree from the result set. */ protected class LawTreeRowCallbackHandler implements RowCallbackHandler { private LinkedHashMap<String, LawTreeNode> treeNodeMap = new LinkedHashMap<>(); private LawInfo info; private LawTreeNode root = null; private String lawId; private LocalDate publishedDate; public LawTreeRowCallbackHandler(LawInfo info) { this.info = info; } /** {@inheritDoc} */ @Override public void processRow(ResultSet rs) throws SQLException { String docId = rs.getString("document_id"); String parentDocId = rs.getString("parent_doc_id"); LawTreeNode node = new LawTreeNode(lawDocInfoRowMapper.mapRow(rs, 0), rs.getInt("sequence_no")); node.setRepealedDate(getLocalDateFromRs(rs, "repealed_date")); treeNodeMap.put(docId, node); if (root == null) { root = node; lawId = rs.getString("law_id"); publishedDate = getLocalDateFromRs(rs, "tree_published_date"); } if (parentDocId != null) { if (!treeNodeMap.containsKey(parentDocId)) { throw new DataIntegrityViolationException("Error while constructing tree, parent ref " + parentDocId + " not in result set."); } treeNodeMap.get(parentDocId).addChild(node); } } /** * Returns a newly constructed LawTree based on the rows processed from the result set. * @return LawTree */ public LawTree getLawTree() { if (lawId == null) { throw new DataRetrievalFailureException("Failed to construct LawTree, since there was no " + "matching root node"); } LawTree tree = new LawTree(new LawVersionId(lawId, publishedDate), root, info); tree.rebuildLookupMap(); return tree; } } /** * Constructs LawDocInfo from result set. */ protected static RowMapper<LawDocInfo> lawDocInfoRowMapper = (rs, rowNum) -> new LawDocInfo(rs.getString("document_id"), rs.getString("law_id"), rs.getString("location_id"), rs.getString("title"), LawDocumentType.valueOf(rs.getString("document_type")), rs.getString("document_type_id"), getLocalDateFromRs(rs, "published_date")); /** * Constructs a LawInfo from the result set. */ protected static RowMapper<LawInfo> lawInfoRowMapper = (rs, rowNum) -> { LawInfo lawInfo = new LawInfo(); lawInfo.setLawId(rs.getString("law_id")); lawInfo.setChapterId(rs.getString("chapter_id")); lawInfo.setName(rs.getString("name")); lawInfo.setType(LawType.valueOf(rs.getString("law_type"))); return lawInfo; }; /** * Constructs LawDocInfo from result set. */ protected static RowMapper<LawDocument> lawDocRowMapper = (rs, rowNum) -> new LawDocument(lawDocInfoRowMapper.mapRow(rs, rowNum), rs.getString("text")); /** --- Param Source Methods --- */ protected MapSqlParameterSource getLawDocumentParams(LawFile lawFile, LawDocument lawDocument) { return new MapSqlParameterSource() .addValue("documentId", lawDocument.getDocumentId()) .addValue("publishedDate", toDate(lawDocument.getPublishedDate())) .addValue("documentType", lawDocument.getDocType().name()) .addValue("lawId", lawDocument.getLawId()) .addValue("locationId", lawDocument.getLocationId()) .addValue("documentTypeId", lawDocument.getDocTypeId()) .addValue("title", lawDocument.getTitle()) .addValue("text", lawDocument.getText()) .addValue("lawFileName", (lawFile != null) ? lawFile.getFileName() : null); } protected MapSqlParameterSource getLawInfoParams(LawInfo lawInfo) { return new MapSqlParameterSource() .addValue("lawId", lawInfo.getLawId()) .addValue("chapterId", lawInfo.getChapterId()) .addValue("lawType", lawInfo.getType().name()) .addValue("name", lawInfo.getName()); } protected MapSqlParameterSource getLawTreeParams(LawTree lawTree) { return new MapSqlParameterSource() .addValue("lawId", lawTree.getLawId()) .addValue("publishedDate", toDate(lawTree.getPublishedDate())); } protected MapSqlParameterSource getLawTreeNodeParams(LawFile lawFile, LawTree lawTree, LawTreeNode lawTreeNode) { return getLawTreeParams(lawTree) .addValue("docId", lawTreeNode.getDocumentId()) .addValue("docPublishedDate", toDate(lawTreeNode.getPublishDate())) .addValue("parentDocId", (lawTreeNode.getParent() != null) ? lawTreeNode.getParent().getDocumentId() : null) .addValue("parentDocPublishedDate", (lawTreeNode.getParent() != null) ? toDate(lawTreeNode.getParent().getPublishDate()) : null) .addValue("isRoot", lawTreeNode.isRootNode()) .addValue("sequenceNo", lawTreeNode.getSequenceNo()) .addValue("repealedDate", toDate(lawTreeNode.getRepealedDate())) .addValue("lawFileName", lawFile.getFileName()); } }