blob: 2d2722377cd762721831122857764f4f7621337e [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2018 ANSYS medini Technologies AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* ANSYS medini Technologies AG - initial API and implementation
******************************************************************************/
package org.eclipse.opencert.elastic.search;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.http.HttpHost;
import org.eclipse.opencert.elastic.ElasticClient;
import org.eclipse.opencert.elastic.ElasticDocument;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import com.google.gson.JsonObject;
/**
* Factory for the {@link ElasticFinder}.
*
* @author mauersberger
*/
public class ElasticFinderImpl implements ElasticFinder {
// XXX hack to get some API tests running
private Collection<Object> dummyData = null;
private RestClient client;
private String index = "_all";
@SuppressWarnings("unused")
private int lastStatus;
/**
* Factory method to create a new {@link ElasticFinder}. XXX This is an
* intermediate feature.
*
* @param data
* @return
*/
public static ElasticFinder onDummy(Object data) {
ElasticFinderImpl impl = new ElasticFinderImpl();
impl.dummyData = DummyData.INSTANCE.convert(data);
return impl;
}
public static ElasticFinder onDefaultClient() {
RestClient client = RestClient.builder(new HttpHost("localhost", 9200, "http")).build();
return onClient(client);
}
public static ElasticFinder onClient(RestClient client) {
ElasticFinderImpl impl = new ElasticFinderImpl();
impl.client = client;
return impl;
}
public static ElasticFinder onClient(ElasticClient client) {
ElasticFinderImpl impl = new ElasticFinderImpl();
impl.client = RestClient.builder(client.endPoint()).build();;
return impl;
}
/*
* Intentionally private.
*/
private ElasticFinderImpl() {
// just to avoid instantiation, force using the factory API
}
@Override
public ElasticFinder within(String index) {
this.index = (index != null ? index : "_all");
return this;
}
@Override
public Set<Hit> search(String query, Map<String, Object> filters) {
if (dummyData != null) {
return searchInDummyData(query, filters);
}
try {
Set<Hit> hits = new HashSet<>();
Set<ElasticDocument> documents = this.searchInElastic(index, query, filters);
for (ElasticDocument document : documents) {
Hit hit = new Hit();
// we count 1 for string match + number of filters
hit.score = 1 + (filters != null ? filters.size() : 0);
hit.objectId = document.id;
hits.add(hit);
}
return hits;
} catch (Exception e) {
e.printStackTrace();
return Collections.emptySet();
}
}
private Set<ElasticDocument> searchInElastic(String indexName, String queryString, Map<String, Object> filters) throws Exception {
RestHighLevelClient highLevel = new RestHighLevelClient(this.client);
SearchRequest searchRequest = new SearchRequest(indexName);
// see example at https://dzone.com/articles/java-high-level-rest-client-elasticsearch
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
searchRequest.source(sourceBuilder);
// find the best query builder depending on the arguments
QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
if (queryString != null) {
queryString = queryString.replaceAll("\\.\\*", "*");
QueryStringQueryBuilder stringQuery = QueryBuilders.queryStringQuery(queryString);
stringQuery.allowLeadingWildcard(true);
queryBuilder = stringQuery;
}
// for each filter we add a separate query that MUST match
if (filters != null && filters.size() > 0) {
// add the original query
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(queryBuilder);
// now create and add a quers for each field
Set<String> keys = filters.keySet();
for (String key : keys) {
Object value = filters.get(key);
TermQueryBuilder termQuery = QueryBuilders.termQuery(key, value);
boolQuery.must(termQuery);
}
assert boolQuery.must().size() == 1 + filters.size();
queryBuilder = boolQuery;
}
sourceBuilder.query(queryBuilder);
// each SearchHit be returned with an explanation of the hit (ranking)
sourceBuilder.explain(true);
SearchResponse response = highLevel.search(searchRequest);
lastStatus = response.status().getStatus();
Set<ElasticDocument> result = new HashSet<>();
SearchHits hits = response.getHits();
// Note: total hits might be much bigger
for (SearchHit hit : hits.getHits()) {
// convert Hit to Json
JsonObject json = new JsonObject();
// put all source information back into JSON
Map<String, Object> fields = hit.getSource();
Set<String> keys = fields.keySet();
for (String key : keys) {
Object field = fields.get(key);
json.addProperty(key, (String) field.toString());
}
ElasticDocument doc = new ElasticDocument(json, hit.getIndex(), hit.getType(), hit.getId());
result.add(doc);
}
return result;
}
/*
* XXX Hack
*/
private Set<Hit> searchInDummyData(String query, Map<String, Object> filters) {
Set<Hit> hits = new HashSet<>();
for (Object data : dummyData) {
String document = DummyData.INSTANCE.asDocument(data);
if ((document.contains(query) || document.matches(query)) && matchFilters(document, filters)) {
Hit hit = new Hit();
// we count 1 for string match + number of filters
hit.score = 1 + (filters != null ? filters.size() : 0);
hit.objectId = DummyData.INSTANCE.getId(document);
hits.add(hit);
}
}
return hits;
}
/*
* XXX Hack
*/
private boolean matchFilters(String document, Map<String, Object> filters) {
// check filters
if (filters == null || filters.isEmpty()) {
return true;
}
for (String name : filters.keySet()) {
String filter = name + ": " + filters.get(name).toString();
if (!document.contains(filter)) {
return false;
}
}
// all filters passed
return true;
}
}