package models.services;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import managers.FriendshipManager;
import managers.GroupAccountManager;
import managers.MediaManager;
import managers.PostManager;
import models.Account;
import models.Group;
import models.Media;
import models.Post;
import models.enums.LinkType;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import play.Environment;
import play.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
* Created by Iven on 22.12.2014.
*/
@Singleton
public class ElasticsearchService implements IElasticsearchService {
final Logger.ALogger logger = Logger.of(ElasticsearchService.class);
@Inject
PostManager postManger;
@Inject
GroupAccountManager groupAccountManager;
@Inject
FriendshipManager friendshipManager;
@Inject
MediaManager mediaManager;
@Inject
Environment environment;
private Client client = null;
private Config conf = ConfigFactory.load().getConfig("elasticsearch");
private final String ES_SERVER = conf.getString("server");
private final String ES_INDEX = conf.getString("index");
private final String ES_TYPE_USER = conf.getString("userType");
private final String ES_TYPE_GROUP = conf.getString("groupType");
private final String ES_TYPE_POST = conf.getString("postType");
private final String ES_TYPE_MEDIUM = conf.getString("mediumType");
private final int ES_RESULT_SIZE = conf.getInt("searchLimit");
private final String ES_SETTINGS = "elasticsearch/settings.json";
private final String ES_USER_MAPPING = "elasticsearch/user_mapping.json";
private final String ES_GROUP_MAPPING = "elasticsearch/group_mapping.json";
private final String ES_POST_MAPPING = "elasticsearch/post_mapping.json";
private final String ES_MEDIUM_MAPPING = "elasticsearch/medium_mapping.json";
public ElasticsearchService() {
try {
client = TransportClient.builder().build()
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ES_SERVER), 9300));
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
public Client getClient() {
return client;
}
public void closeClient() {
logger.info("closing ES client...");
client.close();
logger.info("ES client closed");
}
public boolean isClientAvailable() {
if (((TransportClient) client).connectedNodes().size() == 0)
return false;
return true;
}
public boolean isIndexExists() {
return client.admin().indices().exists(new IndicesExistsRequest(ES_INDEX)).actionGet().isExists();
}
public void deleteIndex() {
if (isClientAvailable()) client.admin().indices().delete(new DeleteIndexRequest(ES_INDEX)).actionGet();
}
public void createAnalyzer() {
if (isClientAvailable()) client.admin().indices().prepareCreate(ES_INDEX)
.setSettings(loadFromFile(ES_SETTINGS))
.execute().actionGet();
}
public void createMapping() {
if (isClientAvailable()) client.admin().indices().preparePutMapping(ES_INDEX).setType(ES_TYPE_USER)
.setSource(loadFromFile(ES_USER_MAPPING))
.execute().actionGet();
if (isClientAvailable()) client.admin().indices().preparePutMapping(ES_INDEX).setType(ES_TYPE_POST)
.setSource(loadFromFile(ES_POST_MAPPING))
.execute().actionGet();
if (isClientAvailable()) client.admin().indices().preparePutMapping(ES_INDEX).setType(ES_TYPE_GROUP)
.setSource(loadFromFile(ES_GROUP_MAPPING))
.execute().actionGet();
if (isClientAvailable()) client.admin().indices().preparePutMapping(ES_INDEX).setType(ES_TYPE_MEDIUM)
.setSource(loadFromFile(ES_MEDIUM_MAPPING))
.execute().actionGet();
}
public void index(Object model) throws IOException {
if (model instanceof Post) indexPost(((Post) model));
if (model instanceof Group) indexGroup(((Group) model));
if (model instanceof Account) indexAccount(((Account) model));
if (model instanceof Media) indexMedium(((Media) model));
}
private void indexPost(Post post) throws IOException {
if (isClientAvailable()) client.prepareIndex(ES_INDEX, ES_TYPE_POST, post.id.toString())
.setSource(jsonBuilder()
.startObject()
.field("content", post.content)
.field("owner", post.owner.id)
.field("public", postManger.isPublic(post))
.field("viewable", postManger.findAllowedToViewAccountIds(post))
.endObject())
.execute()
.actionGet();
}
private void indexGroup(Group group) throws IOException {
if (isClientAvailable()) client.prepareIndex(ES_INDEX, ES_TYPE_GROUP, group.id.toString())
.setSource(jsonBuilder()
.startObject()
.field("title", group.title)
.field("grouptype", group.groupType)
.field("public", true)
.field("owner", group.owner.id)
.field("avatar", group.hasAvatar)
.field("member", groupAccountManager.findAccountIdsByGroup(group, LinkType.establish))
.endObject())
.execute()
.actionGet();
}
private void indexAccount(Account account) throws IOException {
if (isClientAvailable()) client.prepareIndex(ES_INDEX, ES_TYPE_USER, account.id.toString())
.setSource(jsonBuilder()
.startObject()
.field("name", account.name)
.field("studycourse", account.studycourse != null ? account.studycourse.title : "")
.field("degree", account.degree != null ? account.degree : "")
.field("semester", account.semester != null ? String.valueOf(account.semester) : "")
.field("role", account.role != null ? account.role.getDisplayName() : "")
.field("initial", account.getInitials())
.field("avatar", account.avatar)
.field("public", true)
.field("friends", friendshipManager.findFriendsId(account))
.endObject())
.execute()
.actionGet();
}
private void indexMedium(Media medium) throws IOException {
if (isClientAvailable()) client.prepareIndex(ES_INDEX, ES_TYPE_MEDIUM, medium.id.toString())
.setSource(jsonBuilder()
.startObject()
.field("owner", medium.owner.id)
.field("filename", medium.fileName)
.field("viewable", mediaManager.findAllowedToViewAccountIds(medium))
.field("public", mediaManager.isPublic(medium))
.endObject())
.execute()
.actionGet();
}
/**
* Build search query based on all provided fields
*
* @param caller - Define normal search or autocomplete
* @param query - Terms to search for (e.g. 'informatik')
* @param filter - Filter for searchfacets (e.g. user, group, comment)
* @param page - Which results should be shown (e.g. 1: 1-10 ; 2: 11-20 etc.)
* @param currentAccountId - AccountId from user who is logged in (for scoring)
* @param mustFields - All fields to search on
* @param scoringFields - All fields which affect the scoring
* @return - JSON response from Elasticsearch
* @throws ExecutionException
* @throws InterruptedException
*/
@Override
public SearchResponse doSearch(String caller, String query, String filter, HashMap<String, String[]> facets, int page, String currentAccountId, List<String> mustFields, List<String> scoringFields) throws ExecutionException, InterruptedException {
QueryBuilder searchQuery;
if (query.isEmpty() || query == null) {
// Build searchQuery to search for everything
searchQuery = QueryBuilders.matchAllQuery();
} else {
// Build searchQuery by provided fields (mustFields) to search on
searchQuery = QueryBuilders.multiMatchQuery(query, mustFields.toArray(new String[mustFields.size()]));
}
// Build scoringQuery by provided fields (shouldFields) to increase the scoring of a better matching hit
QueryBuilder scoringQuery = QueryBuilders.multiMatchQuery(currentAccountId, scoringFields.toArray(new String[scoringFields.size()]));
// Build boolQuery to enable filter possibilities
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// Add should filter to show authorized posts only
boolQuery.should(QueryBuilders.termQuery("viewable", currentAccountId)).should(QueryBuilders.termQuery("public", true));
// Add mode-filter to filter only for users/group or posts
if (!filter.equals("all")) {
boolQuery.must(QueryBuilders.typeQuery(filter));
}
// Add facet-filter to filter for mode related stuff (eg. user -> students or group -> open)
if (facets != null) {
if (facets.get("studycourse").length != 0) {
for (String facet : facets.get("studycourse")) {
boolQuery.must(QueryBuilders.termQuery("studycourse", facet));
}
}
if (facets.get("degree").length != 0) {
for (String facet : facets.get("degree")) {
boolQuery.must(QueryBuilders.termQuery("degree", facet));
}
}
if (facets.get("semester").length != 0) {
for (String facet : facets.get("semester")) {
boolQuery.must(QueryBuilders.termQuery("semester", facet));
}
}
if (facets.get("role").length != 0) {
for (String facet : facets.get("role")) {
boolQuery.must(QueryBuilders.termQuery("role", facet));
}
}
if (facets.get("grouptype").length != 0) {
for (String facet : facets.get("grouptype")) {
boolQuery.must(QueryBuilders.termQuery("grouptype", facet));
}
}
}
// Build completeQuery with search- and scoringQuery
QueryBuilder completeQuery = QueryBuilders.boolQuery().must(searchQuery).should(scoringQuery).filter(boolQuery);
// Build searchRequest which will be executed after fields to highlight are added.
SearchRequestBuilder searchRequest = client.prepareSearch(ES_INDEX)
.setQuery(completeQuery);
// Add highlighting on all fields to search on
for (String field : mustFields) {
searchRequest.addHighlightedField(field);
}
// Define html tags for highlighting
searchRequest = searchRequest.setHighlighterPreTags("[startStrong]").setHighlighterPostTags("[endStrong]").setHighlighterNumOfFragments(0);
// Enable pagination
searchRequest = searchRequest.setFrom((page * ES_RESULT_SIZE) - ES_RESULT_SIZE);
// Add term aggregation for facet count
searchRequest = searchRequest.addAggregation(AggregationBuilders.terms("types").field("_type"));
// Add user aggregations
if (filter.equals("user")) {
searchRequest = searchRequest.addAggregation(AggregationBuilders.terms("studycourse").field("studycourse"));
searchRequest = searchRequest.addAggregation(AggregationBuilders.terms("degree").field("degree"));
searchRequest = searchRequest.addAggregation(AggregationBuilders.terms("semester").field("semester"));
searchRequest = searchRequest.addAggregation(AggregationBuilders.terms("role").field("role"));
}
// Add group aggregations
if (filter.equals("group")) {
searchRequest = searchRequest.addAggregation(AggregationBuilders.terms("grouptype").field("grouptype"));
}
// Apply PostFilter if request mode is not 'all'
/**final BoolFilterBuilder boolFilterBuilder2 = boolFilter();
if(boolFilterBuilder2.hasClauses()) {
searchRequest.setPostFilter(boolFilterBuilder2);
}*/
//logger.info(searchRequest.toString());
// Execute searchRequest
SearchResponse response = searchRequest.execute().get();
//logger.info(response.toString());
return response;
}
public void delete(Object model) {
if (model instanceof Post) deletePost(((Post) model));
if (model instanceof Group) deleteGroup(((Group) model));
if (model instanceof Account) deleteAccount(((Account) model));
}
private void deleteGroup(Group group) {
if (isClientAvailable()) client.prepareDelete(ES_INDEX, ES_TYPE_GROUP, group.id.toString())
.execute()
.actionGet();
}
private void deletePost(Post post) {
if (isClientAvailable()) client.prepareDelete(ES_INDEX, ES_TYPE_POST, post.id.toString())
.execute()
.actionGet();
}
private void deleteAccount(Account account) {
if (isClientAvailable()) client.prepareDelete(ES_INDEX, ES_TYPE_USER, account.id.toString())
.execute()
.actionGet();
}
private String loadFromFile(String filePath) {
Scanner s = new Scanner(environment.resourceAsStream(filePath)).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
}