blob: 5c21375b3881ba70232d5b8c8fb689b137e8a403 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.api.odsadapter.search;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.lang3.StringEscapeUtils;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.Measurement;
import org.eclipse.mdm.api.base.model.Test;
import org.eclipse.mdm.api.base.model.TestStep;
import org.eclipse.mdm.api.base.query.DataAccessException;
import org.eclipse.mdm.api.odsadapter.lookup.EntityLoader;
import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig.Key;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* This class handles the requests which are sent to the FreeTextSearch
*
* @author Christian Weyermann
*
*/
public class ODSFreeTextSearch {
/**
* This is the payload which needs to be added to the post to add a
*/
private static final String ES_POSTDATA = "{\"query\":{\"simple_query_string\":{\"query\":\"%s\",\"default_operator\":\"or\",\"lenient\":\"true\",\"fields\":[\"name^2\",\"_all\"]}}}";
/**
* mainly logs requests on INFO
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ODSFreeTextSearch.class);
/**
* Used to finally load the Entites
*/
private EntityLoader loader;
/**
* The URL is hard coded
*/
private String url;
/**
* The client is created once and reused
*/
private HttpClient client;
/**
* This will start up the FreeText Search. No upfron querries are done. Thus
* this can be called as often as desired without any major performance loss
*
* @param entityLoader
* @param sourceName
* @throws DataAccessException
*/
public ODSFreeTextSearch(EntityLoader entityLoader, String sourceName, String host) throws DataAccessException {
this.loader = entityLoader;
url = host + "/" + sourceName.toLowerCase() + "/_search?fields=_type,_id,_index&size=50";
client = new HttpClient();
}
/**
* A search which is compatible to the Search as defined in the rest of the
* API.
*
* @param inputQuery
* @return never null, but maybe empty
*/
public Map<Class<? extends Entity>, List<Entity>> search(String inputQuery) {
Map<Class<? extends Entity>, List<Entity>> result = new HashMap<>();
Map<Class<? extends Entity>, List<String>> instances = searchIds(inputQuery);
instances.keySet().forEach(type -> convertIds2Entities(result, instances, type));
return result;
}
/**
* A search which is compatible to the Search as defined in the rest of the
* API.
*
* @param inputQuery
* @return never null, but maybe empty
*/
public Map<Class<? extends Entity>, List<String>> searchIds(String inputQuery) {
Map<Class<? extends Entity>, List<String>> instanceIds = new HashMap<>();
JsonElement root = queryElasticSearch(inputQuery);
if (root != null) {
JsonArray hits = root.getAsJsonObject().get("hits").getAsJsonObject().get("hits").getAsJsonArray();
hits.forEach(e -> put(e, instanceIds));
}
return instanceIds;
}
/**
* Converts all instances to entities
*
* @param convertedMap
* it will
* @param map
* @param type
*/
private void convertIds2Entities(Map<Class<? extends Entity>, List<Entity>> convertedMap,
Map<Class<? extends Entity>, List<String>> map, Class<? extends Entity> type) {
try {
List<Entity> list = new ArrayList<>();
list.addAll(loader.loadAll(new Key<>(type), map.get(type)));
convertedMap.put(type, list);
} catch (DataAccessException e) {
throw new IllegalStateException("Cannot load ids from ODS. This means no results are available", e);
}
}
/**
* Puts all the hits in elasticsearch
*
* @param hit
* a hit as given from ElasticSearch
* @param map
* the map of all ids for the class of the entity
*/
private void put(JsonElement hit, Map<Class<? extends Entity>, List<String>> map) {
JsonObject object = hit.getAsJsonObject();
String type = object.get("_type").getAsString();
Class<? extends Entity> clazz = getClass4Type(type);
if (clazz != null) {
if (!map.containsKey(clazz)) {
List<String> list = new ArrayList<>();
map.put(clazz, list);
}
List<String> list = map.get(clazz);
list.add((String) object.get("_id").getAsString());
}
}
/**
*
* @param type
* the type as given by elasticsearch
* @return the class of each element
*/
private Class<? extends Entity> getClass4Type(String type) {
Class<? extends Entity> clazz;
switch (type) {
case "TestStep":
clazz = TestStep.class;
break;
case "Measurement":
clazz = Measurement.class;
break;
case "Test":
clazz = Test.class;
break;
default:
clazz = null;
}
return clazz;
}
/**
* This method actually querries ElasticSearch.
*
* @param inputQuery
* @return
*/
private JsonElement queryElasticSearch(String inputQuery) {
PostMethod post = new PostMethod(url);
String requestJson = buildRequestJson(inputQuery);
LOGGER.info("POST: " + url);
LOGGER.info("Asking: " + requestJson);
byte[] json = requestJson.getBytes();
post.setRequestEntity(new ByteArrayRequestEntity(json, "application/json"));
JsonElement result = execute(post);
LOGGER.info("Answered:" + result);
return result;
}
/**
* Actually builds the json
*
* @param inputQuery
* @return
*/
private String buildRequestJson(String inputQuery) {
String query = StringEscapeUtils.escapeJson(inputQuery);
return String.format(ES_POSTDATA, query);
}
/**
* Executes the HTTP method and expects a json in the return payload, which
* is then returned
*
* @param method
* @return
*/
private JsonElement execute(HttpMethod method) {
try {
int status = client.executeMethod(method);
if (status == 404) {
return null;
}
checkError(status);
return buildResponseJson(method);
} catch (IOException e) {
throw new IllegalStateException("Problems querying ElasticSearch.", e);
}
}
/**
* Reads out the http method and builds the JSON via GSON
*
* @param method
* @return
* @throws IOException
*/
private JsonElement buildResponseJson(HttpMethod method) throws IOException {
JsonElement res = null;
InputStream in = method.getResponseBodyAsStream();
try (InputStreamReader reader = new InputStreamReader(in)) {
res = new JsonParser().parse(reader);
}
return res;
}
/**
* If an error occured an appropriate exception is thrown.
*
* @param status
*/
private void checkError(int status) {
String text = String.format("ElasticSearch answered %d. ", status);
if (status / 100 != 2) {
throw new IllegalStateException(text);
}
}
}