package gov.nysenate.openleg.service.entity.member.search;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import gov.nysenate.openleg.config.Environment;
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.entity.member.search.ElasticMemberSearchDao;
import gov.nysenate.openleg.model.base.SessionYear;
import gov.nysenate.openleg.model.entity.Chamber;
import gov.nysenate.openleg.model.entity.SessionMember;
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.entity.member.data.MemberService;
import gov.nysenate.openleg.service.entity.member.event.BulkMemberUpdateEvent;
import gov.nysenate.openleg.service.entity.member.event.MemberUpdateEvent;
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.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ElasticMemberSearchService implements MemberSearchService, IndexedSearchService<SessionMember>
{
private static final Logger logger = LoggerFactory.getLogger(ElasticMemberSearchService.class);
@Autowired protected Environment env;
@Autowired protected EventBus eventBus;
@Autowired protected ElasticMemberSearchDao memberSearchDao;
@Autowired protected MemberService memberDataService;
@PostConstruct
protected void init() {
eventBus.register(this);
}
/** {@inheritDoc} */
@Override
public SearchResults<SessionMember> searchMembers(SessionYear sessionYear, String sort, LimitOffset limOff) throws SearchException {
return search(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchAllQuery())
.filter(QueryBuilders.termQuery("sessionYear", sessionYear.getYear())),
null, sort, limOff);
}
@Override
public SearchResults<SessionMember> searchMembers(SessionYear sessionYear, Chamber chamber, String sort, LimitOffset limOff) throws SearchException {
String query = "(chamber:" + chamber.toString() + ") AND (sessionYear:" + sessionYear.getYear() + ")";
return search(QueryBuilders.queryStringQuery(query), null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public SearchResults<SessionMember> searchMembers(String query, String sort, LimitOffset limOff) throws SearchException {
return search(QueryBuilders.queryStringQuery(query), null, sort, limOff);
}
/** {@inheritDoc} */
@Override
public SearchResults<SessionMember> searchMembers(String query, SessionYear sessionYear, String sort, LimitOffset limOff) throws SearchException {
return search(
QueryBuilders.boolQuery()
.must(QueryBuilders.queryStringQuery(query))
.filter(QueryBuilders.termQuery("sessionYear", sessionYear.getYear())),
null, sort, limOff);
}
private SearchResults<SessionMember> search(QueryBuilder query, QueryBuilder postFilter, String sort, LimitOffset limOff)
throws SearchException {
if (limOff == null) limOff = LimitOffset.TWENTY_FIVE;
try {
return memberSearchDao.searchMembers(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
public void updateIndex(SessionMember member) {
if (env.isElasticIndexing() && member != null) {
logger.info("Indexing member {} into elastic search.", member.getLbdcShortName());
memberSearchDao.updateMemberIndex(member);
}
}
/** {@inheritDoc} */
@Override
public void updateIndex(Collection<SessionMember> members) {
if (env.isElasticIndexing() && !members.isEmpty()) {
List<SessionMember> indexableMembers = members.stream().filter(t -> t != null).collect(Collectors.toList());
logger.info("Indexing {} valid members into elastic search.", indexableMembers.size());
memberSearchDao.updateMemberIndex(indexableMembers);
}
}
/** {@inheritDoc} */
@Override
public void clearIndex() {
memberSearchDao.purgeIndices();
memberSearchDao.createIndices();
}
/** {@inheritDoc} */
@Override
public void rebuildIndex() {
clearIndex();
LimitOffset limOff = LimitOffset.HUNDRED;
SortOrder sortOrder = SortOrder.ASC;
List<SessionMember> members;
do {
members = memberDataService.getAllMembers(sortOrder, limOff);
logger.info("Indexing {} members", members.size());
updateIndex(members);
limOff = limOff.next();
members = memberDataService.getAllMembers(sortOrder, limOff);
}
while(!members.isEmpty());
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleRebuildEvent(RebuildIndexEvent event) {
if (event.affects(SearchIndex.MEMBER)) {
logger.info("Handling member re-index event");
try {
rebuildIndex();
} catch (Exception ex) {
logger.error("Unexpected exception during handling of member index rebuild event.", ex);
}
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleClearEvent(ClearIndexEvent event) {
if (event.affects(SearchIndex.MEMBER)) {
clearIndex();
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleMemberUpdate(MemberUpdateEvent memberUpdateEvent) {
if (memberUpdateEvent.getMember() != null) {
updateIndex(memberUpdateEvent.getMember());
}
}
/** {@inheritDoc} */
@Override
@Subscribe
public void handleBulkMemberUpdate(BulkMemberUpdateEvent bulkMemberUpdateEvent) {
if (bulkMemberUpdateEvent.getMembers() != null) {
updateIndex(bulkMemberUpdateEvent.getMembers());
}
}
}