package gov.nysenate.openleg.service.hearing.search;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import gov.nysenate.openleg.dao.base.LimitOffset;
import gov.nysenate.openleg.dao.base.SearchIndex;
import gov.nysenate.openleg.dao.base.SortOrder;
import gov.nysenate.openleg.dao.hearing.search.ElasticPublicHearingSearchDao;
import gov.nysenate.openleg.config.Environment;
import gov.nysenate.openleg.model.hearing.PublicHearing;
import gov.nysenate.openleg.model.hearing.PublicHearingId;
import gov.nysenate.openleg.model.search.*;
import gov.nysenate.openleg.service.base.search.ElasticSearchServiceUtils;
import gov.nysenate.openleg.service.base.search.IndexedSearchService;
import gov.nysenate.openleg.service.hearing.event.BulkPublicHearingUpdateEvent;
import gov.nysenate.openleg.service.hearing.event.PublicHearingUpdateEvent;
import gov.nysenate.openleg.service.hearing.data.PublicHearingDataService;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ElasticPublicHearingSearchService implements PublicHearingSearchService, IndexedSearchService<PublicHearing>
{
private static final Logger logger = LoggerFactory.getLogger(PublicHearingSearchService.class);
@Autowired protected Environment env;
@Autowired protected EventBus eventBus;
@Autowired protected ElasticPublicHearingSearchDao publicHearingSearchDao;
@Autowired protected PublicHearingDataService publicHearingDataService;
@PostConstruct
protected void init() {
eventBus.register(this);
}
@Override
public SearchResults<PublicHearingId> searchPublicHearings(String sort, LimitOffset limOff) throws SearchException {
return search(QueryBuilders.matchAllQuery(), null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public SearchResults<PublicHearingId> searchPublicHearings(int year, String sort, LimitOffset limOff) throws SearchException {
RangeQueryBuilder rangeFilter = QueryBuilders.rangeQuery("date")
.from(LocalDate.of(year, 1, 1))
.to(LocalDate.of(year, 12, 31));
return search(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchAllQuery())
.filter(rangeFilter),
null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public SearchResults<PublicHearingId> searchPublicHearings(String query, String sort, LimitOffset limOff) throws SearchException {
return search(QueryBuilders.queryStringQuery(query), null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public SearchResults<PublicHearingId> searchPublicHearings(String query, int year, String sort, LimitOffset limOff) throws SearchException {
RangeQueryBuilder rangeFilter = QueryBuilders.rangeQuery("date")
.from(LocalDate.of(year, 1, 1))
.to(LocalDate.of(year, 12, 31));
return search(
QueryBuilders.boolQuery()
.must(QueryBuilders.queryStringQuery(query))
.filter(rangeFilter),
null, sort, limOff);
}
private SearchResults<PublicHearingId> search(QueryBuilder query, QueryBuilder postFilter, String sort, LimitOffset limOff)
throws SearchException {
if (limOff == null) limOff = LimitOffset.TEN;
try {
return publicHearingSearchDao.searchPublicHearings(query, postFilter,
ElasticSearchServiceUtils.extractSortBuilders(sort), limOff);
}
catch (SearchParseException ex) {
throw new SearchException("Invalid query string", ex);
}
catch (ElasticsearchException ex) {
throw new UnexpectedSearchException(ex);
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handlePublicHearingUpdate(PublicHearingUpdateEvent publicHearingUpdateEvent) {
if (publicHearingUpdateEvent.getPublicHearing() != null) {
updateIndex(publicHearingUpdateEvent.getPublicHearing());
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleBulkPublicHearingUpdate(BulkPublicHearingUpdateEvent bulkPublicHearingUpdateEvent) {
if (bulkPublicHearingUpdateEvent.getPublicHearings() != null) {
updateIndex(bulkPublicHearingUpdateEvent.getPublicHearings());
}
}
/** {@inheritDoc} */
@Override
public void updateIndex(PublicHearing publicHearing) {
if (env.isElasticIndexing() && publicHearing != null) {
logger.info("Indexing public hearing {} into elastic search.", publicHearing.getTitle());
publicHearingSearchDao.updatePublicHearingIndex(publicHearing);
}
}
/** {@inheritDoc} */
@Override
public void updateIndex(Collection<PublicHearing> publicHearings) {
if (env.isElasticIndexing() && !publicHearings.isEmpty()) {
List<PublicHearing> indexablePublicHearings = publicHearings.stream().filter(ph -> ph != null).collect(Collectors.toList());
logger.info("Indexing {} public hearings into elastic search.", indexablePublicHearings.size());
publicHearingSearchDao.updatePublicHearingIndex(indexablePublicHearings);
}
}
/** {@inheritDoc} */
@Override
public void clearIndex() {
publicHearingSearchDao.purgeIndices();
publicHearingSearchDao.createIndices();
}
/** {@inheritDoc} */
@Override
public void rebuildIndex() {
clearIndex();
for (int year = 2011; year <= LocalDate.now().getYear(); year++) {
LimitOffset limitOffset = LimitOffset.TWENTY_FIVE;
List<PublicHearingId> publicHearingIds = publicHearingDataService.getPublicHearingIds(SortOrder.DESC, limitOffset);
while (!publicHearingIds.isEmpty()) {
logger.info("Indexing {} public hearings starting from {}.", publicHearingIds.size(), year);
List<PublicHearing> publicHearings = publicHearingIds.stream().map(publicHearingDataService::getPublicHearing).collect(Collectors.toList());
updateIndex(publicHearings);
limitOffset = limitOffset.next();
publicHearingIds = publicHearingDataService.getPublicHearingIds(SortOrder.DESC, limitOffset);
}
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleRebuildEvent(RebuildIndexEvent event) {
if (event.affects(SearchIndex.HEARING)) {
logger.info("Handling public hearing re-index event.");
try {
rebuildIndex();
} catch (Exception ex) {
logger.error("Unexpected exception during handling of public hearing index rebuild event.", ex);
}
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleClearEvent(ClearIndexEvent event) {
if (event.affects(SearchIndex.HEARING)) {
clearIndex();
}
}
}