package gov.nysenate.openleg.service.agenda.search;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import gov.nysenate.openleg.dao.agenda.search.ElasticAgendaSearchDao;
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.model.agenda.Agenda;
import gov.nysenate.openleg.model.agenda.AgendaId;
import gov.nysenate.openleg.model.agenda.CommitteeAgendaId;
import gov.nysenate.openleg.config.Environment;
import gov.nysenate.openleg.model.base.SessionYear;
import gov.nysenate.openleg.model.search.*;
import gov.nysenate.openleg.service.agenda.data.AgendaDataService;
import gov.nysenate.openleg.service.agenda.event.AgendaUpdateEvent;
import gov.nysenate.openleg.service.agenda.event.BulkAgendaUpdateEvent;
import gov.nysenate.openleg.service.base.search.ElasticSearchServiceUtils;
import gov.nysenate.openleg.service.base.search.IndexedSearchService;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
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 static java.util.stream.Collectors.toList;
@Service
public class ElasticAgendaSearchService implements AgendaSearchService, IndexedSearchService<Agenda>
{
private static final Logger logger = LoggerFactory.getLogger(ElasticAgendaSearchService.class);
@Autowired private Environment env;
@Autowired private EventBus eventBus;
@Autowired private ElasticAgendaSearchDao agendaSearchDao;
@Autowired private AgendaDataService agendaDataService;
@PostConstruct
protected void init() {
eventBus.register(this);
}
/** {@inheritDoc} */
@Override
public SearchResults<CommitteeAgendaId> searchCommitteeAgendas(String query, String sort, LimitOffset limOff) throws SearchException {
return searchCommitteeAgendas(QueryBuilders.queryStringQuery(query), null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public SearchResults<CommitteeAgendaId> searchCommitteeAgendas(int year, String sort, LimitOffset limOff) throws SearchException {
return searchCommitteeAgendas(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchAllQuery())
.filter(QueryBuilders.termQuery("agenda.id.year", year)),
null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public SearchResults<CommitteeAgendaId> searchCommitteeAgendas(String query, int year, String sort, LimitOffset limOff) throws SearchException {
return searchCommitteeAgendas(
QueryBuilders.boolQuery()
.must(QueryBuilders.queryStringQuery(query))
.filter(QueryBuilders.termQuery("agenda.id.year", year)),
null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public void updateIndex(Agenda agenda) {
if (env.isElasticIndexing()) {
logger.info("Indexing agenda {} into elastic search.", agenda.getId());
agendaSearchDao.updateAgendaIndex(agenda);
}
}
/** {@inheritDoc} */
@Override
public void updateIndex(Collection<Agenda> agendas) {
if (env.isElasticIndexing()) {
logger.info("Indexing {} agendas into elastic search.", agendas.size());
agendaSearchDao.updateAgendaIndex(agendas);
}
}
/** {@inheritDoc} */
@Override
public void clearIndex() {
agendaSearchDao.purgeIndices();
agendaSearchDao.createIndices();
}
/** {@inheritDoc} */
@Override
public void rebuildIndex() {
clearIndex();
for (int year = 2009; year <= LocalDate.now().getYear(); year++) {
List<AgendaId> agendaIds = agendaDataService.getAgendaIds(year, SortOrder.ASC);
List<Agenda> agendas = agendaIds.stream().map(aid -> agendaDataService.getAgenda(aid)).collect(toList());
logger.info("Reindexing {} agendas from {}", agendas.size(), year);
agendaSearchDao.updateAgendaIndex(agendas);
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleRebuildEvent(RebuildIndexEvent event) {
if (event.affects(SearchIndex.AGENDA)) {
logger.info("Handling agenda re-index event");
try {
rebuildIndex();
}
catch (Exception ex) {
logger.error("Unexpected exception during handling of Agenda RebuildIndexEvent!", ex);
}
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleClearEvent(ClearIndexEvent event) {
if (event.affects(SearchIndex.AGENDA)) {
clearIndex();
}
}
/** {@inheritDoc} */
@Subscribe
@Override
public synchronized void handleAgendaUpdateEvent(AgendaUpdateEvent agendaUpdateEvent) {
if (agendaUpdateEvent != null && agendaUpdateEvent.getAgenda() != null) {
updateIndex(agendaUpdateEvent.getAgenda());
}
}
/** {@inheritDoc} */
@Subscribe
@Override
public synchronized void handleBulkAgendaUpdateEvent(BulkAgendaUpdateEvent bulkAgendaUpdateEvent) {
if (bulkAgendaUpdateEvent != null && !bulkAgendaUpdateEvent.getAgendas().isEmpty()) {
updateIndex(bulkAgendaUpdateEvent.getAgendas());
}
}
/** --- Internal Methods --- */
private SearchResults<CommitteeAgendaId> searchCommitteeAgendas(QueryBuilder query, QueryBuilder postFilter,
String sort, LimitOffset limitOffset) throws SearchException {
if (limitOffset == null) {
limitOffset = LimitOffset.ALL;
}
try {
return agendaSearchDao.searchCommitteeAgendas(query, postFilter,
ElasticSearchServiceUtils.extractSortBuilders(sort), limitOffset);
} catch (SearchParseException ex) {
throw new SearchException("There was a problem parsing the supplied query string.", ex);
} catch (ElasticsearchException ex) {
throw new UnexpectedSearchException(ex);
}
}
}