Commit 2761dab2 authored by neelesh thakur's avatar neelesh thakur
Browse files

implmenent sort on text for all providers

parent 702be0b3
......@@ -319,7 +319,7 @@ If you need to use date in your query, it has to be in one of the following form
For more info please refer [Date format](http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateOptionalTimeParser--)
## Sort <a name="sort-queries"></a>
The sort feature supports int, float, double, long and datetime, but it does not support array object, nested object or string field as of now, and for the records contain such types won't return in the response.
The sort feature supports string, int, float, double, long and datetime, but it does not support array object or nested object as of now, and for the records contain such types won't return in the response.
The records either does not have the sorted fields or have empty value will be listed last in the result.
......@@ -339,7 +339,7 @@ E.g. Given
}
}
```
The above request payload asks search service to sort on "data.Id" in an ascending order, and the expected response will have "totalCount: 10" (instead of 20, please note that the 10 returned records are only from common:welldb:wellbore:1.0.0 because the data.Id in common:welldb:well:1.0.0 is of data type string, which is not currently supported - and therefore, will not be returned) and should list the 5 records which have empty data.Id value at last.
The above request payload asks search service to sort on "data.Id" in ascending order, and the expected response will have "totalCount: 10" (instead of 20, please note that the 10 returned records are only from common:welldb:wellbore:1.0.0 because the data.Id in common:welldb:well:1.0.0 is of data type string, which is not currently supported - and therefore, will not be returned) and should list the 5 records which have empty data.Id value at last.
**NOTE:** Search service does not validate the provided sort field, whether it exists or is of the supported data types. Different kinds may have attributes with the same names, but are different data types. Therefore, it is the user's responsibility to be aware and validate this in one's own workflow.
......
// Copyright © Amazon Web Services
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.opengroup.osdu.search.provider.aws.cache;
import org.opengroup.osdu.core.common.cache.RedisCache;
import org.opengroup.osdu.search.cache.IFieldTypeMappingCache;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class FieldTypeMappingCacheImpl implements IFieldTypeMappingCache {
private RedisCache<String, Map> cache;
/**
* Initializes a Cursor Cache with Redis connection parameters specified in the application
* properties file.
*
* @param REDIS_SEARCH_HOST - the hostname of the Cursor Cache Redis cluster.
* @param REDIS_SEARCH_PORT - the port of the Cursor Cache Redis cluster.
*/
public FieldTypeMappingCacheImpl(@Value("${aws.elasticache.cluster.cursor.endpoint}") final String REDIS_SEARCH_HOST,
@Value("${aws.elasticache.cluster.cursor.port}") final String REDIS_SEARCH_PORT) {
cache = new RedisCache<String, Map>(REDIS_SEARCH_HOST, Integer.parseInt(REDIS_SEARCH_PORT),
5 * 60, String.class, Map.class);
}
/**
* Insert a Map object into the Redis cache
*
* @param s the key of the object, used for retrieval
* @param o the Map object to store
*/
@Override
public void put(String s, Map o) {
this.cache.put(s, o);
}
/**
* Gets a cached Map object by key
*
* @param s the key of the cached Map object to get
* @return
*/
@Override
public Map get(String s) {
return this.cache.get(s);
}
/**
* Deletes a Map item in the cache with the given key
*
* @param s the key of the cached Map object to delete
*/
@Override
public void delete(String s) {
this.cache.delete(s);
}
/**
* Clears the entire Redis cache
*/
@Override
public void clearAll() {
this.cache.clearAll();
}
}
......@@ -37,7 +37,6 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.locationtech.jts.geom.Coordinate;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.core.common.model.entitlements.AclRole;
......@@ -46,8 +45,9 @@ import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.core.common.model.search.*;
import org.opengroup.osdu.search.policy.service.IPolicyService;
import org.opengroup.osdu.search.policy.service.PartitionPolicyStatusService;
import org.opengroup.osdu.search.provider.aws.service.FieldMappingTypeService;
import org.opengroup.osdu.search.provider.interfaces.IProviderHeaderService;
import org.opengroup.osdu.search.service.IFieldMappingTypeService;
import org.opengroup.osdu.search.query.builder.SortQueryBuilder;
import org.opengroup.osdu.search.util.CrossTenantUtils;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -55,7 +55,6 @@ import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import static org.elasticsearch.index.query.QueryBuilders.*;
......@@ -72,7 +71,9 @@ abstract class QueryBase {
@Inject
private CrossTenantUtils crossTenantUtils;
@Inject
private FieldMappingTypeService fieldMappingTypeService;
private IFieldMappingTypeService fieldMappingTypeService;
@Autowired
private SortQueryBuilder sortQueryBuilder;
@Autowired(required = false)
private IPolicyService iPolicyService;
@Inject
......@@ -272,18 +273,6 @@ abstract class QueryBase {
sourceBuilder.highlighter(highlightBuilder);
}
// sort: text is not suitable for sorting or aggregation, refer to: this: https://github.com/elastic/elasticsearch/issues/28638,
// so keyword is recommended for unmappedType in general because it can handle both string and number.
// It will ignore the characters longer than the threshold when sorting.
if (request.getSort() != null) {
for (int idx = 0; idx < request.getSort().getField().size(); idx++) {
sourceBuilder.sort(new FieldSortBuilder(request.getSort().getFieldByIndex(idx))
.order(SortOrder.fromString(request.getSort().getOrderByIndex(idx).name()))
.missing("_last")
.unmappedType("keyword"));
}
}
// set the return fields
List<String> returnedFields = request.getReturnedFields();
if (returnedFields == null) {
......@@ -309,11 +298,18 @@ abstract class QueryBase {
SearchResponse searchResponse = null;
try {
String index = this.getIndex(searchRequest);
if (searchRequest.getSpatialFilter() != null) {
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, this.getIndex(searchRequest));
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, index);
}
elasticSearchRequest = createElasticRequest(searchRequest);
if (searchRequest.getSort() != null) {
List<FieldSortBuilder> sortBuilders = this.sortQueryBuilder.getSortQuery(client, searchRequest.getSort(), index);
for (FieldSortBuilder fieldSortBuilder : sortBuilders) {
elasticSearchRequest.source().sort(fieldSortBuilder);
}
}
startTime = System.currentTimeMillis();
searchResponse = client.search(elasticSearchRequest, RequestOptions.DEFAULT);
return searchResponse;
......@@ -322,7 +318,7 @@ abstract class QueryBase {
case NOT_FOUND:
throw new AppException(HttpServletResponse.SC_NOT_FOUND, "Not Found", "Resource you are trying to find does not exists", e);
case BAD_REQUEST:
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", "Invalid parameters were given on search request", e);
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", getDetailedBadRequestMessage(elasticSearchRequest, e), e);
case SERVICE_UNAVAILABLE:
throw new AppException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Search error", "Please re-try search after some time.", e);
default:
......@@ -363,4 +359,26 @@ abstract class QueryBase {
}
this.queryFailedAuditLogger(searchRequest);
}
private String getDetailedBadRequestMessage(SearchRequest searchRequest, Exception e) {
String defaultErrorMessage = "Invalid parameters were given on search request";
if (e.getCause() == null) return defaultErrorMessage;
String msg = getKeywordFieldErrorMessage(searchRequest, e.getCause().getMessage());
if (msg != null) return msg;
return defaultErrorMessage;
}
private String getKeywordFieldErrorMessage(SearchRequest searchRequest, String msg) {
if (msg == null) return null;
if (msg.contains("Text fields are not optimised for operations that require per-document field data like aggregations and sorting")
|| msg.contains("can't sort on geo_shape field without using specific sorting feature, like geo_distance")) {
if (searchRequest.source().sorts() != null && !searchRequest.source().sorts().isEmpty()) {
return "Sort is not supported for one or more of the requested fields";
}
if (searchRequest.source().aggregations() != null && searchRequest.source().aggregations().count() > 0) {
return "Aggregations are not supported for one or more of the specified fields";
}
}
return null;
}
}
\ No newline at end of file
// Copyright 2017-2019, Schlumberger
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.opengroup.osdu.search.provider.aws.service;
import com.google.common.base.Strings;
import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest;
import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.opengroup.osdu.core.common.search.Preconditions;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@Component
@RequestScope
public class FieldMappingTypeService {
public Set<String> getFieldTypes(RestHighLevelClient restClient, String fieldName, String indexPattern) throws IOException {
Preconditions.checkNotNull(restClient, "restClient cannot be null");
Preconditions.checkNotNullOrEmpty(fieldName, "fieldName cannot be null or empty");
Preconditions.checkNotNullOrEmpty(indexPattern, "indexPattern cannot be null or empty");
Set<String> fieldTypes = new HashSet<>();
String fieldLeafNodeLabel = fieldName.substring(fieldName.lastIndexOf(".") + 1);
GetFieldMappingsRequest request = new GetFieldMappingsRequest();
request.fields(fieldName);
if (!Strings.isNullOrEmpty(indexPattern)) request.indices(indexPattern);
GetFieldMappingsResponse response = restClient.indices().getFieldMapping(request, RequestOptions.DEFAULT);
Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> mappings = response.mappings();
for (Map.Entry<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> indexMapping : mappings.entrySet()) {
if (indexMapping.getValue().isEmpty()) continue;
Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>> typeMapping = indexMapping.getValue();
Map<String, GetFieldMappingsResponse.FieldMappingMetadata> fieldMapping = typeMapping.values().iterator().next();
GetFieldMappingsResponse.FieldMappingMetadata fieldMappingMetaData = fieldMapping.get(fieldName);
if (fieldMappingMetaData == null) continue;
Map<String, Object> mapping = fieldMappingMetaData.sourceAsMap();
LinkedHashMap<String, Object> typeMap = (LinkedHashMap<String, Object>) mapping.get(fieldLeafNodeLabel);
Object type = typeMap.get("type");
if (type == null) continue;
fieldTypes.add(type.toString());
}
return fieldTypes;
}
}
\ No newline at end of file
package org.opengroup.osdu.search.provider.azure.cache.impl;
import org.opengroup.osdu.core.common.cache.VmCache;
import org.opengroup.osdu.search.cache.IFieldTypeMappingCache;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class FieldTypeMappingCacheImpl extends VmCache<String, Map> implements IFieldTypeMappingCache {
public FieldTypeMappingCacheImpl() {
super(1440 * 60, 1000);
}
}
\ No newline at end of file
......@@ -46,8 +46,8 @@ import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.core.common.model.search.*;
import org.opengroup.osdu.search.policy.service.IPolicyService;
import org.opengroup.osdu.search.policy.service.PartitionPolicyStatusService;
import org.opengroup.osdu.search.provider.azure.service.FieldMappingTypeService;
import org.opengroup.osdu.search.provider.azure.service.SortQueryBuilder;
import org.opengroup.osdu.search.service.IFieldMappingTypeService;
import org.opengroup.osdu.search.query.builder.SortQueryBuilder;
import org.opengroup.osdu.search.provider.interfaces.IProviderHeaderService;
import org.opengroup.osdu.search.util.CrossTenantUtils;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -70,10 +70,9 @@ abstract class QueryBase {
@Inject
private CrossTenantUtils crossTenantUtils;
@Inject
private FieldMappingTypeService fieldMappingTypeService;
@Inject
private IFieldMappingTypeService fieldMappingTypeService;
@Autowired
private SortQueryBuilder sortQueryBuilder;
@Autowired(required = false)
private IPolicyService iPolicyService;
@Inject
......@@ -298,13 +297,13 @@ abstract class QueryBase {
SearchResponse searchResponse = null;
try {
String index = this.getIndex(searchRequest);
if (searchRequest.getSpatialFilter() != null) {
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, this.getIndex(searchRequest));
useGeoShapeQuery = this.useGeoShapeQuery(client, searchRequest, index);
}
elasticSearchRequest = createElasticRequest(searchRequest);
if (searchRequest.getSort() != null) {
List<FieldSortBuilder> sortBuilders = this.sortQueryBuilder.getSortQuery(client, searchRequest.getSort(), this.getIndex(searchRequest));
List<FieldSortBuilder> sortBuilders = this.sortQueryBuilder.getSortQuery(client, searchRequest.getSort(), index);
for (FieldSortBuilder fieldSortBuilder : sortBuilders) {
elasticSearchRequest.source().sort(fieldSortBuilder);
}
......@@ -318,7 +317,7 @@ abstract class QueryBase {
case NOT_FOUND:
throw new AppException(HttpServletResponse.SC_NOT_FOUND, "Not Found", "Resource you are trying to find does not exists", e);
case BAD_REQUEST:
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", "Invalid parameters were given on search request", e);
throw new AppException(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", getDetailedBadRequestMessage(elasticSearchRequest, e), e);
case SERVICE_UNAVAILABLE:
throw new AppException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Search error", "Please re-try search after some time.", e);
default:
......@@ -359,4 +358,26 @@ abstract class QueryBase {
}
this.queryFailedAuditLogger(searchRequest);
}
private String getDetailedBadRequestMessage(SearchRequest searchRequest, Exception e) {
String defaultErrorMessage = "Invalid parameters were given on search request";
if (e.getCause() == null) return defaultErrorMessage;
String msg = getKeywordFieldErrorMessage(searchRequest, e.getCause().getMessage());
if (msg != null) return msg;
return defaultErrorMessage;
}
private String getKeywordFieldErrorMessage(SearchRequest searchRequest, String msg) {
if (msg == null) return null;
if (msg.contains("Text fields are not optimised for operations that require per-document field data like aggregations and sorting")
|| msg.contains("can't sort on geo_shape field without using specific sorting feature, like geo_distance")) {
if (searchRequest.source().sorts() != null && !searchRequest.source().sorts().isEmpty()) {
return "Sort is not supported for one or more of the requested fields";
}
if (searchRequest.source().aggregations() != null && searchRequest.source().aggregations().count() > 0) {
return "Aggregations are not supported for one or more of the specified fields";
}
}
return null;
}
}
\ No newline at end of file
......@@ -46,7 +46,7 @@ import org.opengroup.osdu.core.common.model.search.QueryRequest;
import org.opengroup.osdu.core.common.model.search.QueryResponse;
import org.opengroup.osdu.core.common.model.search.SpatialFilter;
import org.opengroup.osdu.search.logging.AuditLogger;
import org.opengroup.osdu.search.provider.azure.service.FieldMappingTypeService;
import org.opengroup.osdu.search.service.FieldMappingTypeService;
import org.opengroup.osdu.search.provider.interfaces.IProviderHeaderService;
import org.opengroup.osdu.search.util.CrossTenantUtils;
import org.opengroup.osdu.search.util.ElasticClientHandler;
......
//// Copyright © Microsoft Corporation
////
//// Licensed under the Apache License, Version 2.0 (the "License");
//// you may not use this file except in compliance with the License.
//// You may obtain a copy of the License at
////
//// http://www.apache.org/licenses/LICENSE-2.0
////
//// Unless required by applicable law or agreed to in writing, software
//// distributed under the License is distributed on an "AS IS" BASIS,
//// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//// See the License for the specific language governing permissions and
//// limitations under the License.
//
//package org.opengroup.osdu.search.provider.azure.service;
//
//import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest;
//import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse;
//import org.elasticsearch.client.IndicesClient;
//import org.elasticsearch.client.RestHighLevelClient;
//import org.junit.Test;
//import org.junit.runner.RunWith;
//import org.mockito.InjectMocks;
//import org.mockito.junit.MockitoJUnitRunner;
//
//import java.io.IOException;
//import java.util.*;
//
//import static org.junit.Assert.assertEquals;
//import static org.mockito.Mockito.any;
//import static org.mockito.Mockito.doReturn;
//import static org.mockito.Mockito.mock;
//
//@RunWith(MockitoJUnitRunner.class)
//public class FieldMappingTypeServiceTest {
//
// private static final String dummyTypeObjectStringRepresentation = "dummyTypeObject";
// private static final String FIELD = "field";
// private static final String TYPE = "type";
//
// private class DummyTypeObject {
// @Override
// public String toString() {
// return dummyTypeObjectStringRepresentation;
// }
// }
//
// @InjectMocks
// private FieldMappingTypeService sut;
//
// /*
// * NOTE [aaljain] :
// * Scenarios where typeMap and fieldMapping are null will result into error
// * which the current implementation of FieldMappingTypeService does not handle
// */
//
// @Test
// public void testGetFieldTypes_whenAllMappingsProvided_returnsCorrectFieldTypes() throws IOException {
// RestHighLevelClient restClient = mock(RestHighLevelClient.class);
// GetFieldMappingsResponse response = mock(GetFieldMappingsResponse.class);
// GetFieldMappingsResponse.FieldMappingMetadata fieldMappingMetaData = mock(GetFieldMappingsResponse.FieldMappingMetadata.class);
// IndicesClient indicesClient = mock(IndicesClient.class);
//
// String fieldName = FIELD + "." + TYPE;
// String indexPattern = "index.pattern";
//
// Map<String, Object> sourceMap = getDummySourceMap();
// Map<String, GetFieldMappingsResponse.FieldMappingMetadata> fieldMapping = new HashMap<>();
// fieldMapping.put(fieldName, fieldMappingMetaData);
// Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>> typeMapping = new HashMap<>();
// typeMapping.put(FIELD, fieldMapping);
// Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> indexMapping = new HashMap<>();
// indexMapping.put(TYPE, typeMapping);
//
// doReturn(indicesClient).when(restClient).indices();
// doReturn(response).when(indicesClient).getFieldMapping(any(GetFieldMappingsRequest.class), any());
// doReturn(indexMapping).when(response).mappings();
// doReturn(sourceMap).when(fieldMappingMetaData).sourceAsMap();
//
// Set<String> fieldTypes = sut.getFieldTypes(restClient, fieldName, indexPattern);
//
// assertEquals(fieldTypes.size(), 1);
// assertEquals(fieldTypes.iterator().next(), dummyTypeObjectStringRepresentation);
// }
//
// @Test
// public void testGetFieldTypes_whenMissingIndexMapping_returnsEmptyFieldTypes() throws IOException {
// RestHighLevelClient restClient = mock(RestHighLevelClient.class);
// GetFieldMappingsResponse response = mock(GetFieldMappingsResponse.class);
// IndicesClient indicesClient = mock(IndicesClient.class);
//
// String fieldName = FIELD + "." + TYPE;
// String indexPattern = "index.pattern";
//
// Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> indexMapping = new HashMap<>();
//
// doReturn(indicesClient).when(restClient).indices();
// doReturn(response).when(indicesClient).getFieldMapping(any(GetFieldMappingsRequest.class), any());
// doReturn(indexMapping).when(response).mappings();
//
// Set<String> fieldTypes = sut.getFieldTypes(restClient, fieldName, indexPattern);
//
// assertEquals(fieldTypes.size(), 0);
// }
//
// @Test
// public void testGetFieldTypes_whenMissingFieldMappingMetaData_returnsEmptyFieldTypes() throws IOException {
// RestHighLevelClient restClient = mock(RestHighLevelClient.class);
// GetFieldMappingsResponse response = mock(GetFieldMappingsResponse.class);
// IndicesClient indicesClient = mock(IndicesClient.class);
//
// String fieldName = FIELD + "." + TYPE;
// String indexPattern = "index.pattern";
//
// Map<String, GetFieldMappingsResponse.FieldMappingMetadata> fieldMapping = new HashMap<>();
// Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>> typeMapping = new HashMap<>();
// typeMapping.put(FIELD, fieldMapping);
// Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> indexMapping = new HashMap<>();
// indexMapping.put(TYPE, typeMapping);
//
// doReturn(indicesClient).when(restClient).indices();
// doReturn(response).when(indicesClient).getFieldMapping(any(GetFieldMappingsRequest.class), any());
// doReturn(indexMapping).when(response).mappings();
//
// Set<String> fieldTypes = sut.getFieldTypes(restClient, fieldName, indexPattern);
//
// assertEquals(fieldTypes.size(), 0);
// }
//
// @Test
// public void testGetFieldTypes_whenMissingTypeObjectInTypeMap_returnsEmptyFieldTypes() throws IOException {
// RestHighLevelClient restClient = mock(RestHighLevelClient.class);
// GetFieldMappingsResponse response = mock(GetFieldMappingsResponse.class);
// GetFieldMappingsResponse.FieldMappingMetadata fieldMappingMetaData = mock(GetFieldMappingsResponse.FieldMappingMetadata.class);
// IndicesClient indicesClient = mock(IndicesClient.class);
//
// String fieldName = FIELD + "." + TYPE;
// String indexPattern = "index.pattern";
//
// Map<String, Object> sourceMap = getDummySourceMap();
// ((LinkedHashMap) sourceMap.get(TYPE)).put(TYPE, null);
// Map<String, GetFieldMappingsResponse.FieldMappingMetadata> fieldMapping = new HashMap<>();
// fieldMapping.put(fieldName, fieldMappingMetaData);
// Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>> typeMapping = new HashMap<>();
// typeMapping.put(FIELD, fieldMapping);
// Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetadata>>> indexMapping = new HashMap<>();
// indexMapping.put(TYPE, typeMapping);
//
// doReturn(indicesClient).when(restClient).indices();
// doReturn(response).when(indicesClient).getFieldMapping(any(GetFieldMappingsRequest.class), any());
// doReturn(indexMapping).when(response).mappings();
// doReturn(sourceMap).when(fieldMappingMetaData).sourceAsMap();
//
// Set<String> fieldTypes = sut.getFieldTypes(restClient, fieldName, indexPattern);
//
// assertEquals(fieldTypes.size(), 0);
// }
//
// private Map<String, Object> getDummySourceMap() {
// Map<String, Object> sourceMap = new HashMap<>();
// LinkedHashMap typeMap = new LinkedHashMap<>();
// Object type = new DummyTypeObject();
// typeMap.put(TYPE, type);
// sourceMap.put(TYPE, typeMap);
// return sourceMap;
// }
//}
\ No newline at end of file