package sagan.search.support;
import io.searchbox.core.Search;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.facet.terms.TermsFacet;
import org.elasticsearch.search.facet.terms.TermsFacetBuilder;
import org.elasticsearch.search.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Pageable;
import sagan.search.types.SearchType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class SaganQueryBuilders {
private static final String TODAY = "now/d";
private static final String BOOSTED_TITLE_FIELD = "title^3";
private static final String RAW_CONTENT_FIELD = "rawContent";
private static final String AUTHOR_FIELD = "author";
static Search.Builder fullTextSearch(String queryTerm, Pageable pageable, List<String> filters) {
BoolQueryBuilder query = QueryBuilders.boolQuery()
.must(matchTitleContentAndAuthor(queryTerm))
.should(matchMarkedAsCurrent())
.should(matchProjectPages())
.should(matchPhrase(queryTerm).boost(3f));
String search = buildSearch(query, filters, pageable);
return new Search.Builder(search);
}
private static MultiMatchQueryBuilder matchTitleContentAndAuthor(String queryTerm) {
return QueryBuilders
.multiMatchQuery(queryTerm, BOOSTED_TITLE_FIELD, RAW_CONTENT_FIELD, AUTHOR_FIELD)
.fuzziness(Fuzziness.ONE)
.minimumShouldMatch("30%");
}
private static TermQueryBuilder matchMarkedAsCurrent() {
return QueryBuilders.termQuery("current", true);
}
private static TermQueryBuilder matchProjectPages() {
return QueryBuilders.termQuery("_type", SearchType.PROJECT_PAGE.toString());
}
private static MatchQueryBuilder matchPhrase(String query) {
return QueryBuilders.matchPhraseQuery("title", query).slop(1);
}
static Search.Builder forEmptyQuery(Pageable pageable, List<String> filters) {
QueryBuilder query = QueryBuilders.boolQuery().should(QueryBuilders.matchAllQuery());
String search = buildSearch(query, filters, pageable);
return new Search.Builder(search);
}
private static String buildSearch(QueryBuilder query, List<String> filters, Pageable pageable) {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
FilterBuilder filterBuilder = filterUnpublishedResults();
QueryBuilder filteredQuery = QueryBuilders.filteredQuery(query, filterBuilder);
addQuery(filteredQuery, searchSourceBuilder);
addFacetPathsResult(searchSourceBuilder);
addHighlights(searchSourceBuilder);
sort(searchSourceBuilder);
addPagination(pageable, searchSourceBuilder);
searchSourceBuilder.postFilter(buildFilterFacets(filters));
return searchSourceBuilder.toString();
}
private static void addPagination(Pageable pageable, SearchSourceBuilder searchSourceBuilder) {
searchSourceBuilder.from(pageable.getOffset());
searchSourceBuilder.size(pageable.getPageSize());
}
private static SearchSourceBuilder addQuery(QueryBuilder query, SearchSourceBuilder searchSourceBuilder) {
return searchSourceBuilder.query(query);
}
private static OrFilterBuilder filterUnpublishedResults() {
return new OrFilterBuilder()
.add(new RangeFilterBuilder("publishAt").lte(TODAY))
.add(new NotFilterBuilder(new TypeFilterBuilder(SearchType.BLOG_POST.toString())));
}
private static OrFilterBuilder buildFilterFacets(List<String> filters) {
OrFilterBuilder outermostFilter = new OrFilterBuilder();
if (filters != null && !filters.isEmpty()) {
Map<String, List<String>> splitFilters = splitFilters(filters);
List<String> projects = splitFilters.get("projects");
List<String> apiRef = splitFilters.get("apiRef");
List<String> otherFacetPaths = splitFilters.get("others");
AndFilterBuilder projectApiRefAnded = new AndFilterBuilder();
if (apiRef.size() > 0) {
projectApiRefAnded.add(new TermsFilterBuilder("facetPaths", apiRef).execution("or"));
}
if (projects.size() > 0) {
projectApiRefAnded.add(new TermsFilterBuilder("facetPaths", projects).execution("or"));
}
outermostFilter.add(projectApiRefAnded);
if (otherFacetPaths.size() > 0) {
outermostFilter.add(new TermsFilterBuilder("facetPaths", otherFacetPaths).execution("or"));
}
}
return outermostFilter;
}
private static Map<String, List<String>> splitFilters(List<String> filters) {
ArrayList<String> projects = new ArrayList<>();
ArrayList<String> apiRef = new ArrayList<>();
ArrayList<String> others = new ArrayList<>();
for (String filter : filters) {
if (filter.startsWith("Projects")) {
if (filter.equals("Projects/Api") || filter.equals("Projects/Reference")) {
apiRef.add(filter);
}
else {
projects.add(filter);
}
}
else {
others.add(filter);
}
}
HashMap<String, List<String>> splitFilters = new HashMap<>();
splitFilters.put("projects", projects);
splitFilters.put("apiRef", apiRef);
splitFilters.put("others", others);
return splitFilters;
}
private static void addFacetPathsResult(SearchSourceBuilder searchSourceBuilder) {
TermsFacetBuilder facetBuilder = new TermsFacetBuilder("facet_paths_result")
.field("facetPaths")
.order(TermsFacet.ComparatorType.TERM).size(100000);
searchSourceBuilder.facet(facetBuilder);
}
private static void addHighlights(SearchSourceBuilder searchSourceBuilder) {
HighlightBuilder highlightBuilder =
new HighlightBuilder().order("score").requireFieldMatch(false).field("rawContent", 300, 1);
searchSourceBuilder.highlight(highlightBuilder);
}
private static void sort(SearchSourceBuilder searchSourceBuilder) {
searchSourceBuilder
.sort(SortBuilders.scoreSort().order(SortOrder.DESC))
.sort(SortBuilders.fieldSort("publishAt").order(SortOrder.DESC));
}
static String wrapQuery(String query) {
return new StringBuilder().append("{\"query\":\n").append(query).append("\n}").toString();
}
static FilteredQueryBuilder matchUnsupportedProjectEntries(String projectId, List<String> supportedVersions) {
QueryBuilder query = QueryBuilders.matchAllQuery();
OrFilterBuilder supportedVersionsFilter = matchSupportedVersions(supportedVersions);
NotFilterBuilder notSupportedVersionFilter = new NotFilterBuilder(supportedVersionsFilter);
TermFilterBuilder projectFilter = new TermFilterBuilder("projectId", projectId);
AndFilterBuilder filter = new AndFilterBuilder(projectFilter, notSupportedVersionFilter);
return QueryBuilders.filteredQuery(query, filter);
}
private static OrFilterBuilder matchSupportedVersions(List<String> supportedVersions) {
OrFilterBuilder orFilter = new OrFilterBuilder();
for (String supportedVersion : supportedVersions) {
orFilter.add(new TermFilterBuilder("version", supportedVersion));
}
return orFilter;
}
}