diff --git a/indexer-core/pom.xml b/indexer-core/pom.xml index 2aefa834bad5cffdb6b39da78b359f4d8fd03cfb..04c37e8b3300ca0cb8bcc526ccf9085307a11df8 100644 --- a/indexer-core/pom.xml +++ b/indexer-core/pom.xml @@ -18,7 +18,6 @@ <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>os-core-common</artifactId> - <version>0.0.13</version> </dependency> <!-- spring boot dependencies --> diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/RecordIndexerApi.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/RecordIndexerApi.java index cfa3f23415108db2b88c95386b28de7d7679789f..5962141825b4a3c6bf33ddec01c270a560e27dc9 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/RecordIndexerApi.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/RecordIndexerApi.java @@ -1,100 +1,99 @@ -// 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.indexer.api; - -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; - -import com.google.gson.JsonParseException; -import io.swagger.annotations.ApiOperation; -import lombok.extern.java.Log; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.search.RecordChangedMessages; -import org.opengroup.osdu.indexer.SwaggerDoc; -import org.opengroup.osdu.core.common.model.indexer.JobStatus; -import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; -import org.opengroup.osdu.indexer.service.IndexerService; -import org.opengroup.osdu.indexer.service.ReindexService; -import org.opengroup.osdu.core.common.model.indexer.RecordInfo; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.annotation.RequestScope; - -import javax.inject.Inject; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import java.lang.reflect.Type; -import java.util.List; - -@Log -@RestController -@RequestMapping("/_dps/task-handlers") -@RequestScope -public class RecordIndexerApi { - - @Inject - private IndexerService indexerService; - @Inject - private ReindexService reIndexService; - - // THIS IS AN INTERNAL USE API ONLY - // THAT MEANS WE DON'T DOCUMENT IT IN SWAGGER, ACCESS IS LIMITED TO CLOUD TASK QUEUE CALLS ONLY - @PostMapping(path = "/index-worker", consumes = "application/json") - @ApiOperation(hidden = true, value = "", notes = "") - public ResponseEntity<JobStatus> indexWorker ( - @NotNull(message = SwaggerDoc.REQUEST_VALIDATION_NOT_NULL_BODY) - @Valid @RequestBody RecordChangedMessages recordChangedMessages) throws Exception { - - if (recordChangedMessages.missingAccountId()) { - throw new AppException(org.apache.http.HttpStatus.SC_BAD_REQUEST, "Invalid tenant", - String.format("Required header: '%s' not found", DpsHeaders.DATA_PARTITION_ID)); - } - try { - if (recordChangedMessages == null) { - log.info("record change messages is null"); - } - - Type listType = new TypeToken<List<RecordInfo>>() { - }.getType(); - List<RecordInfo> recordInfos = new Gson().fromJson(recordChangedMessages.getData(), listType); - - if (recordInfos.size() == 0) { - log.info("none of record-change message can be deserialized"); - return new ResponseEntity(HttpStatus.OK); - } - this.indexerService.processRecordChangedMessages(recordChangedMessages, recordInfos); - return new ResponseEntity(HttpStatus.OK); - } catch (AppException e) { - throw e; - } catch (JsonParseException e) { - throw new AppException(org.apache.http.HttpStatus.SC_BAD_REQUEST, "Request payload parsing error", "Unable to parse request payload.", e); - } catch (Exception e) { - throw new AppException(org.apache.http.HttpStatus.SC_BAD_REQUEST, "Unknown error", "An unknown error has occurred.", e); - } - } - - // THIS IS AN INTERNAL USE API ONLY - // THAT MEANS WE DON'T DOCUMENT IT IN SWAGGER, ACCESS IS LIMITED TO CLOUD TASK QUEUE CALLS ONLY - @PostMapping("/reindex-worker") - @ApiOperation(hidden = true, value = "", notes = "") - public ResponseEntity reindex( - @RequestBody @NotNull(message = SwaggerDoc.REQUEST_VALIDATION_NOT_NULL_BODY) - @Valid RecordReindexRequest recordReindexRequest) { - - return new ResponseEntity(reIndexService.reindexRecords(recordReindexRequest),HttpStatus.OK); - } -} \ 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.indexer.api; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; + +import com.google.gson.JsonParseException; +import io.swagger.annotations.ApiOperation; +import lombok.extern.java.Log; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.search.RecordChangedMessages; +import org.opengroup.osdu.indexer.SwaggerDoc; +import org.opengroup.osdu.core.common.model.indexer.JobStatus; +import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; +import org.opengroup.osdu.indexer.service.IndexerService; +import org.opengroup.osdu.indexer.service.ReindexService; +import org.opengroup.osdu.core.common.model.indexer.RecordInfo; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.annotation.RequestScope; + +import javax.inject.Inject; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.lang.reflect.Type; +import java.util.List; + +@Log +@RestController +@RequestMapping("/_dps/task-handlers") +@RequestScope +public class RecordIndexerApi { + + @Inject + private IndexerService indexerService; + @Inject + private ReindexService reIndexService; + + // THIS IS AN INTERNAL USE API ONLY + // THAT MEANS WE DON'T DOCUMENT IT IN SWAGGER, ACCESS IS LIMITED TO CLOUD TASK QUEUE CALLS ONLY + @PostMapping(path = "/index-worker", consumes = "application/json") + @ApiOperation(hidden = true, value = "", notes = "") + public ResponseEntity<JobStatus> indexWorker ( + @NotNull(message = SwaggerDoc.REQUEST_VALIDATION_NOT_NULL_BODY) + @Valid @RequestBody RecordChangedMessages recordChangedMessages) throws Exception { + + if (recordChangedMessages.missingAccountId()) { + throw new AppException(org.apache.http.HttpStatus.SC_BAD_REQUEST, "Invalid tenant", + String.format("Required header: '%s' not found", DpsHeaders.DATA_PARTITION_ID)); + } + try { + if (recordChangedMessages == null) { + log.info("record change messages is null"); + } + + Type listType = new TypeToken<List<RecordInfo>>() { + }.getType(); + List<RecordInfo> recordInfos = new Gson().fromJson(recordChangedMessages.getData(), listType); + + if (recordInfos.size() == 0) { + log.info("none of record-change message can be deserialized"); + return new ResponseEntity(HttpStatus.OK); + } + this.indexerService.processRecordChangedMessages(recordChangedMessages, recordInfos); + return new ResponseEntity(HttpStatus.OK); + } catch (AppException e) { + throw e; + } catch (JsonParseException e) { + throw new AppException(org.apache.http.HttpStatus.SC_BAD_REQUEST, "Request payload parsing error", "Unable to parse request payload.", e); + } catch (Exception e) { + throw new AppException(org.apache.http.HttpStatus.SC_BAD_REQUEST, "Unknown error", "An unknown error has occurred.", e); + } + } + + // THIS IS AN INTERNAL USE API ONLY + // THAT MEANS WE DON'T DOCUMENT IT IN SWAGGER, ACCESS IS LIMITED TO CLOUD TASK QUEUE CALLS ONLY + @PostMapping("/reindex-worker") + @ApiOperation(hidden = true, value = "", notes = "") + public ResponseEntity<?> reindex( + @RequestBody @NotNull(message = SwaggerDoc.REQUEST_VALIDATION_NOT_NULL_BODY) + @Valid RecordReindexRequest recordReindexRequest) { + return new ResponseEntity<>(reIndexService.reindexRecords(recordReindexRequest, false), HttpStatus.OK); + } +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/ReindexApi.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/ReindexApi.java index 5df99479e436aa6a66ef8dd803c549922cdf40ea..0a6f6ba7a14c54fbf7b34f5e4acadf755a75109a 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/ReindexApi.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/api/ReindexApi.java @@ -1,53 +1,57 @@ -// 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.indexer.api; - -import org.opengroup.osdu.core.common.model.search.SearchServiceRole; -import org.opengroup.osdu.indexer.logging.AuditLogger; -import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; -import org.opengroup.osdu.indexer.service.ReindexService; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.annotation.RequestScope; - -import javax.inject.Inject; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; - -import static java.util.Collections.singletonList; - -@RestController -@RequestMapping("/reindex") -@RequestScope -public class ReindexApi { - - @Inject - private ReindexService reIndexService; - @Inject - private AuditLogger auditLogger; - - @PreAuthorize("@authorizationFilter.hasPermission('" + SearchServiceRole.ADMIN + "')") - @PostMapping - public ResponseEntity reindex( - @NotNull @Valid @RequestBody RecordReindexRequest recordReindexRequest) { - this.reIndexService.reindexRecords(recordReindexRequest); - this.auditLogger.getReindex(singletonList(recordReindexRequest.getKind())); - return new ResponseEntity (org.springframework.http.HttpStatus.OK); - } -} \ 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.indexer.api; + +import org.opengroup.osdu.core.common.model.search.SearchServiceRole; +import org.opengroup.osdu.indexer.logging.AuditLogger; +import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; +import org.opengroup.osdu.indexer.service.IndexSchemaService; +import org.opengroup.osdu.indexer.service.ReindexService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; + +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.annotation.RequestScope; + +import javax.inject.Inject; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import java.io.IOException; + +import static java.util.Collections.singletonList; + +@RestController +@RequestMapping("/reindex") +@RequestScope +public class ReindexApi { + + @Inject + private ReindexService reIndexService; + @Inject + private IndexSchemaService indexSchemaService; + @Inject + private AuditLogger auditLogger; + + @PreAuthorize("@authorizationFilter.hasPermission('" + SearchServiceRole.ADMIN + "')") + @PostMapping + public ResponseEntity<?> reindex( + @NotNull @Valid @RequestBody RecordReindexRequest recordReindexRequest, + @RequestParam(value = "force_clean", defaultValue = "false") boolean forceClean) throws IOException { + this.reIndexService.reindexRecords(recordReindexRequest, this.indexSchemaService.isStorageSchemaSyncRequired(recordReindexRequest.getKind(), forceClean)); + this.auditLogger.getReindex(singletonList(recordReindexRequest.getKind())); + return new ResponseEntity<>(org.springframework.http.HttpStatus.OK); + } +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImpl.java index 3188710f54680d8e18a9f99542a19b6fa4fdeb0a..9e9026c815055600e3b2a58da8e2c82317e812cd 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImpl.java @@ -1,165 +1,235 @@ -// 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.indexer.service; - -import com.google.common.base.Strings; -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import com.google.gson.internal.LinkedTreeMap; -import com.google.gson.reflect.TypeToken; -import org.apache.http.HttpStatus; -import org.opengroup.osdu.core.common.model.indexer.IndexingStatus; -import org.opengroup.osdu.core.common.model.indexer.JobStatus; -import org.opengroup.osdu.indexer.util.parser.DateTimeParser; -import org.opengroup.osdu.indexer.util.parser.GeoShapeParser; -import org.opengroup.osdu.indexer.util.parser.NumberParser; -import org.springframework.stereotype.Service; -import org.springframework.web.context.annotation.RequestScope; - -import javax.inject.Inject; -import java.lang.reflect.Type; -import java.util.Map; - -@Service -@RequestScope -public class AttributeParsingServiceImpl implements IAttributeParsingService { - - private static final String GEOJSON = "GeoJSON"; - - @Inject - private NumberParser numberParser; - @Inject - private DateTimeParser dateTimeParser; - @Inject - private GeoShapeParser geoShapeParser; - @Inject - private GeometryConversionService geometryConversionService; - @Inject - private JobStatus jobStatus; - - @Override - public void tryParseInteger(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { - try { - int parsedInteger = this.numberParser.parseInteger(attributeName, attributeVal); - dataMap.put(attributeName, parsedInteger); - } catch (IllegalArgumentException e) { - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); - } - } - - @Override - public void tryParseLong(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { - try { - long parsedLong = this.numberParser.parseLong(attributeName, attributeVal); - dataMap.put(attributeName, parsedLong); - } catch (IllegalArgumentException e) { - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); - } - } - - @Override - public void tryParseFloat(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { - try { - float parsedFloat = this.numberParser.parseFloat(attributeName, attributeVal); - dataMap.put(attributeName, parsedFloat); - } catch (IllegalArgumentException e) { - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); - } - } - - @Override - public void tryParseDouble(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { - try { - double parsedDouble = this.numberParser.parseDouble(attributeName, attributeVal); - dataMap.put(attributeName, parsedDouble); - } catch (IllegalArgumentException e) { - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); - } - } - - @Override - public void tryParseBoolean(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { - String val = attributeVal == null ? null : String.valueOf(attributeVal); - dataMap.put(attributeName, Boolean.parseBoolean(val)); - } - - @Override - public void tryParseDate(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { - String val = attributeVal == null ? null : String.valueOf(attributeVal); - if (Strings.isNullOrEmpty(val)) { - // skip indexing - return; - } - - String utcDate = this.dateTimeParser.convertDateObjectToUtc(val); - if (Strings.isNullOrEmpty(utcDate)) { - String parsingError = String.format("datetime parsing error: unknown format for attribute: %s | value: %s", attributeName, attributeVal); - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, parsingError, String.format("record-id: %s | %s", recordId, parsingError)); - } else { - dataMap.put(attributeName, utcDate); - } - } - - @Override - public void tryParseGeopoint(String recordId, String attributeName, Map<String, Object> storageRecordData, Map<String, Object> dataMap) { - - Object attributeVal = storageRecordData.get(attributeName); - - try { - Type type = new TypeToken<Map<String, Double>>() {}.getType(); - Map<String, Double> positionMap = new Gson().fromJson(attributeVal.toString(), type); - - if (positionMap == null || positionMap.isEmpty()) return; - - Map<String, Double> position = this.geometryConversionService.tryGetGeopoint(positionMap); - - if (position == null || position.isEmpty()) return; - - dataMap.put(attributeName, position); - - // check if geo shape is not there and if it is not then create it in the schema as well as create the data. - LinkedTreeMap<String, Object> map = (LinkedTreeMap) storageRecordData.get(GEOJSON); - if (map == null || map.isEmpty()) { - Map<String, Object> geometry = this.geometryConversionService.getGeopointGeoJson(positionMap); - - if (geometry == null) return; - - dataMap.put(DATA_GEOJSON_TAG, geometry); - } - } catch (JsonSyntaxException | IllegalArgumentException e) { - String parsingError = String.format("geopoint parsing error: %s attribute: %s | value: %s", e.getMessage(), attributeName, attributeVal); - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, parsingError, String.format("record-id: %s | %s", recordId, parsingError)); - } - } - - @Override - public void tryParseGeojson(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { - - try { - Type type = new TypeToken<Map<String, Object>>() {}.getType(); - Map<String, Object> geoJsonMap = new Gson().fromJson(attributeVal.toString(), type); - - if (geoJsonMap == null || geoJsonMap.isEmpty()) return; - - this.geoShapeParser.parseGeoJson(geoJsonMap); - - dataMap.put(attributeName, geoJsonMap); - } catch (JsonSyntaxException | IllegalArgumentException e) { - String parsingError = String.format("geo-json shape parsing error: %s attribute: %s", e.getMessage(), attributeName); - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, parsingError, String.format("record-id: %s | %s", recordId, parsingError)); - } - } -} - +// 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.indexer.service; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.reflect.TypeToken; +import org.apache.http.HttpStatus; +import org.opengroup.osdu.core.common.model.indexer.ElasticType; +import org.opengroup.osdu.core.common.model.indexer.IndexSchema; +import org.opengroup.osdu.core.common.model.indexer.IndexingStatus; +import org.opengroup.osdu.core.common.model.indexer.JobStatus; +import org.opengroup.osdu.core.common.Constants; +import org.opengroup.osdu.indexer.util.parser.BooleanParser; +import org.opengroup.osdu.indexer.util.parser.DateTimeParser; +import org.opengroup.osdu.indexer.util.parser.GeoShapeParser; +import org.opengroup.osdu.indexer.util.parser.NumberParser; +import org.springframework.stereotype.Service; +import org.springframework.web.context.annotation.RequestScope; + +import javax.inject.Inject; +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.util.*; +import java.util.function.BiFunction; + +@Service +@RequestScope +public class AttributeParsingServiceImpl implements IAttributeParsingService { + + private static final String GEOJSON = "GeoJSON"; + private static final String GEOMETRY_COLLECTION = "geometrycollection"; + private static final String GEOMETRIES = "geometries"; + + @Inject + private NumberParser numberParser; + @Inject + private BooleanParser booleanParser; + @Inject + private DateTimeParser dateTimeParser; + @Inject + private GeoShapeParser geoShapeParser; + @Inject + private GeometryConversionService geometryConversionService; + @Inject + private JobStatus jobStatus; + + @Override + public void tryParseValueArray(Class<?> attributeClass, String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + BiFunction<String, Object, ?> parser; + ElasticType elasticType = ElasticType.forValue(attributeClass.getSimpleName()); + switch (elasticType) { + case DOUBLE: + parser = this.numberParser::parseDouble; + break; + case FLOAT: + parser = this.numberParser::parseFloat; + break; + case INTEGER: + parser = this.numberParser::parseInteger; + break; + case LONG: + parser = this.numberParser::parseLong; + break; + case BOOLEAN: + parser = this.booleanParser::parseBoolean; + break; + case DATE: + parser = this.dateTimeParser::parseDate; + // DateTime parser output is String + attributeClass = String.class; + break; + default: + throw new IllegalArgumentException("Invalid array attribute type"); + } + + try { + List<String> parsedStringList = isArrayType(attributeVal); + List out = new ArrayList<>(); + for (Object o : parsedStringList) { + out.add(parser.apply(attributeName, o)); + } + Object parsedAttribute = toTypeArray(attributeClass, out); + dataMap.put(attributeName, parsedAttribute); + } catch (IllegalArgumentException e) { + this.jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); + } + } + + @Override + public void tryParseInteger(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + try { + int parsedInteger = this.numberParser.parseInteger(attributeName, attributeVal); + dataMap.put(attributeName, parsedInteger); + } catch (IllegalArgumentException e) { + jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); + } + } + + @Override + public void tryParseLong(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + try { + long parsedLong = this.numberParser.parseLong(attributeName, attributeVal); + dataMap.put(attributeName, parsedLong); + } catch (IllegalArgumentException e) { + jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); + } + } + + @Override + public void tryParseFloat(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + try { + float parsedFloat = this.numberParser.parseFloat(attributeName, attributeVal); + dataMap.put(attributeName, parsedFloat); + } catch (IllegalArgumentException e) { + jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); + } + } + + @Override + public void tryParseDouble(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + try { + double parsedDouble = this.numberParser.parseDouble(attributeName, attributeVal); + dataMap.put(attributeName, parsedDouble); + } catch (IllegalArgumentException e) { + jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); + } + } + + @Override + public void tryParseBoolean(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + boolean parsedBoolean = this.booleanParser.parseBoolean(attributeName, attributeVal); + dataMap.put(attributeName, parsedBoolean); + } + + @Override + public void tryParseDate(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + try { + String parsedDate = this.dateTimeParser.parseDate(attributeName, attributeVal); + if (parsedDate == null) return; + dataMap.put(attributeName, parsedDate); + } catch (IllegalArgumentException e) { + jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, e.getMessage(), String.format("record-id: %s | %s", recordId, e.getMessage())); + } + } + + @Override + public void tryParseGeopoint(String recordId, String attributeName, Map<String, Object> storageRecordData, Map<String, Object> dataMap) { + + Object attributeVal = storageRecordData.get(attributeName); + + try { + Type type = new TypeToken<Map<String, Double>>() {}.getType(); + Map<String, Double> positionMap = new Gson().fromJson(attributeVal.toString(), type); + + if (positionMap == null || positionMap.isEmpty()) return; + + Map<String, Double> position = this.geometryConversionService.tryGetGeopoint(positionMap); + + if (position == null || position.isEmpty()) return; + + dataMap.put(attributeName, position); + + // check if geo shape is not there and if it is not then create it in the schema as well as create the data. + LinkedTreeMap<String, Object> map = (LinkedTreeMap) storageRecordData.get(GEOJSON); + if (map == null || map.isEmpty()) { + Map<String, Object> geometry = this.geometryConversionService.getGeopointGeoJson(positionMap); + + if (geometry == null) return; + + dataMap.put(DATA_GEOJSON_TAG, geometry); + } + } catch (JsonSyntaxException | IllegalArgumentException e) { + String parsingError = String.format("geopoint parsing error: %s attribute: %s | value: %s", e.getMessage(), attributeName, attributeVal); + jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, parsingError, String.format("record-id: %s | %s", recordId, parsingError)); + } + } + + @Override + public void tryParseGeojson(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap) { + + try { + Type type = new TypeToken<Map<String, Object>>() {}.getType(); + Map<String, Object> geoJsonMap = new Gson().fromJson(attributeVal.toString(), type); + + if (geoJsonMap == null || geoJsonMap.isEmpty()) return; + + this.geoShapeParser.parseGeoJson(geoJsonMap); + + dataMap.put(attributeName, geoJsonMap); + } catch (JsonSyntaxException | IllegalArgumentException e) { + String parsingError = String.format("geo-json shape parsing error: %s attribute: %s", e.getMessage(), attributeName); + jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, parsingError, String.format("record-id: %s | %s", recordId, parsingError)); + } + } + + + private List<String> isArrayType(Object attributeVal) { + try { + String value = attributeVal == null ? null : String.valueOf(attributeVal); + if (attributeVal == null || Strings.isNullOrEmpty(value)) { + return Collections.EMPTY_LIST; + } + + Gson converter = new Gson(); + Type type = new TypeToken<List<String>>() {}.getType(); + return converter.fromJson(value, type); + } catch (JsonSyntaxException e) { + throw new IllegalArgumentException("array parsing error, not a valid array"); + } + } + + private <N> N toTypeArray(Class<N> fieldClass, List<?> list) { + Object array = Array.newInstance(fieldClass, list.size()); + for (int i = 0; i < list.size(); i++) { + Array.set(array, i, list.get(i)); + } + return (N) array; + } +} + diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IAttributeParsingService.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IAttributeParsingService.java index 8d7647d31e4811d5915b0fd73ba7f8c159155aa6..4050444a40b1bef8f184d8b09c80a078d9e73729 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IAttributeParsingService.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IAttributeParsingService.java @@ -9,6 +9,7 @@ public interface IAttributeParsingService { public static final String RECORD_GEOJSON_TAG = "GeoJSON.features.geometry"; public static final String DATA_GEOJSON_TAG = "x-geojson"; + void tryParseValueArray(Class<?> attributeClass, String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap); void tryParseInteger(String recordId, String attributeName, Object attributeVal, Map<String, Object> dataMap); diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaService.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaService.java index e0a7f7a649ec2788162fd43c7cb800e021e07983..7e512ae7af32a70650372cfd2a2e48bed6093e27 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaService.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaService.java @@ -1,29 +1,34 @@ -// 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.indexer.service; - -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.indexer.IndexSchema; -import org.opengroup.osdu.core.common.model.indexer.OperationType; - -import java.io.IOException; -import java.util.Map; - -public interface IndexSchemaService { - - IndexSchema getIndexerInputSchema(String kind) throws AppException; - - void processSchemaMessages(Map<String, OperationType> schemaMsgs) throws IOException; -} \ 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.indexer.service; + +import org.elasticsearch.ElasticsearchException; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.indexer.IndexSchema; +import org.opengroup.osdu.core.common.model.indexer.OperationType; + +import java.io.IOException; +import java.util.Map; + +public interface IndexSchemaService { + + IndexSchema getIndexerInputSchema(String kind, boolean invalidateCached) throws AppException; + + void processSchemaMessages(Map<String, OperationType> schemaMsgs) throws IOException; + + void syncIndexMappingWithStorageSchema(String kind) throws ElasticsearchException, IOException, AppException; + + boolean isStorageSchemaSyncRequired(String kind, boolean forceClean) throws IOException; +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaServiceImpl.java index 6148991bd175232a10733fbe1cd0e24daf70b29e..4d0240b652ebe86c92fe65b7f0d472ad3f52e63f 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexSchemaServiceImpl.java @@ -16,6 +16,7 @@ package org.opengroup.osdu.indexer.service; import com.google.common.base.Strings; import com.google.gson.Gson; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.client.RestHighLevelClient; import org.opengroup.osdu.core.common.model.storage.SchemaItem; @@ -84,7 +85,7 @@ public class IndexSchemaServiceImpl implements IndexSchemaService { if (msg.getValue() == OperationType.create_schema) { // reset cache and get new schema this.invalidateCache(kind); - IndexSchema schemaObj = this.getIndexerInputSchema(kind); + IndexSchema schemaObj = this.getIndexerInputSchema(kind, true); if (schemaObj.isDataSchemaMissing()) { log.warning(String.format("schema not found for kind: %s", kind)); return; @@ -118,7 +119,11 @@ public class IndexSchemaServiceImpl implements IndexSchemaService { } @Override - public IndexSchema getIndexerInputSchema(String kind) throws AppException { + public IndexSchema getIndexerInputSchema(String kind, boolean invalidateCached) throws AppException { + + if (invalidateCached) { + this.invalidateCache(kind); + } try { String schema = (String) this.schemaCache.get(kind); @@ -154,6 +159,26 @@ public class IndexSchemaServiceImpl implements IndexSchemaService { } } + public void syncIndexMappingWithStorageSchema(String kind) throws ElasticsearchException, IOException, AppException { + String index = this.elasticIndexNameResolver.getIndexNameFromKind(kind); + try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { + if (this.indicesService.isIndexExist(restClient, index)) { + this.indicesService.deleteIndex(restClient, index); + this.log.info(String.format("deleted index: %s", index)); + } + IndexSchema schemaObj = this.getIndexerInputSchema(kind, true); + this.indicesService.createIndex(restClient, index, null, schemaObj.getType(), this.mappingService.getIndexMappingFromRecordSchema(schemaObj)); + } + } + + public boolean isStorageSchemaSyncRequired(String kind, boolean forceClean) throws IOException { + String index = this.elasticIndexNameResolver.getIndexNameFromKind(kind); + try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { + boolean indexExist = this.indicesService.isIndexExist(restClient, index); + return !indexExist || forceClean; + } + } + private void invalidateCache(String kind) { String schema = (String) this.schemaCache.get(kind); if (!Strings.isNullOrEmpty(schema)) this.schemaCache.delete(kind); diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceImpl.java index 33d34bac44b4a428df4f10d62a5962939f7fbc5f..8882bd76e5f560f708c0b01b0caa69cbe39b192a 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceImpl.java @@ -1,327 +1,337 @@ -// 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.indexer.service; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.apache.http.HttpStatus; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData; -import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; -import org.elasticsearch.action.support.master.AcknowledgedResponse; -import org.elasticsearch.client.Request; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.Response; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.reindex.BulkByScrollResponse; -import org.elasticsearch.index.reindex.UpdateByQueryRequest; - -import com.google.gson.Gson; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.Constants; -import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; -import org.opengroup.osdu.core.common.search.Preconditions; -import org.opengroup.osdu.core.common.model.indexer.IndexSchema; -import org.opengroup.osdu.core.common.model.indexer.Records; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.indexer.util.ElasticClientHandler; -import org.springframework.stereotype.Service; -import javax.inject.Inject; - -@Service -public class IndexerMappingServiceImpl extends MappingServiceImpl implements IndexerMappingService { - - @Inject - private JaxRsDpsLog log; - @Inject - private ElasticClientHandler elasticClientHandler; - private TimeValue REQUEST_TIMEOUT = TimeValue.timeValueMinutes(1); - - - /** - * Create a new type in Elasticsearch - * - * @param client Elasticsearch client - * @param index Index name - * @param merge Try to merge mapping if type already exists - * @throws IOException if cannot create mapping - */ - public String createMapping(RestHighLevelClient client, IndexSchema schema, String index, boolean merge) throws IOException { - - Map<String, Object> mappingMap = this.getIndexMappingFromRecordSchema(schema); - String mapping = new Gson().toJson(mappingMap, Map.class); - this.createMappingWithJson(client, index, schema.getType(), mapping, merge); - return mapping; - } - - /* - * Read schema mapping - * - * @param schema Index schema - * @param type Mapping type - * @return String JSON represnetation of type and elastic type - * - * sample index mapping: - * "properties": { - * all meta attributes - * "acl": { - * "properties": { - mapping of all roles - * } - * }, - * "legal": { - * "properties": { - * mapping of all legal properties - * } - * } - * "data": { - * "properties": { - * all data-source attributes - * } - * } - * } - * */ - public Map<String, Object> getIndexMappingFromRecordSchema(IndexSchema schema) { - - // entire property block - Map<String, Object> properties = new HashMap<>(); - - // meta attribute - Map<String, Object> metaMapping = new HashMap<>(); - for (Map.Entry<String, Object> entry : schema.getMetaSchema().entrySet()) { - String key = entry.getKey(); - if (key.equals(RecordMetaAttribute.ACL.getValue()) || key.equals(RecordMetaAttribute.LEGAL.getValue()) || key.equals(RecordMetaAttribute.ANCESTRY.getValue()) || key.equals(RecordMetaAttribute.INDEX_STATUS.getValue())) { - metaMapping.put(key, entry.getValue()); - } else { - metaMapping.put(key, Records.Type.builder().type(entry.getValue().toString()).build()); - } - } - - // data-source attributes - Map<String, Object> dataMapping = new HashMap<>(); - if (schema.getDataSchema() != null) { - for (Map.Entry<String, String> entry : schema.getDataSchema().entrySet()) { - dataMapping.put(entry.getKey(), Records.Type.builder().type(entry.getValue()).build()); - } - - // inner properties.data.properties block - Map<String, Object> dataProperties = new HashMap<>(); - dataProperties.put(Constants.PROPERTIES, dataMapping); - - // data & meta block - properties.put(Constants.DATA, dataProperties); - } - properties.putAll(metaMapping); - - // entire document properties block - Map<String, Object> documentMapping = new HashMap<>(); - documentMapping.put(Constants.PROPERTIES, properties); - - // don't add dynamic mapping - documentMapping.put("dynamic", false); - - return documentMapping; - } - - @Override - public void updateIndexMappingForIndicesOfSameType(Set<String> indices, String fieldName) throws Exception { - try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { - if(!updateMappingToEnableKeywordIndexingForField(restClient,indices,fieldName)){ - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error updating index mapping.", String.format("Failed to get confirmation from elastic server mapping update for indices: %s", indices)); - } - } - } - - private boolean updateMappingToEnableKeywordIndexingForField(RestHighLevelClient client, Set<String> indicesSet, String fieldName) throws IOException { - String[] indices = indicesSet.toArray(new String[indicesSet.size()]); - Map<String, Map<String, Map<String, FieldMappingMetaData>>> indexMappingMap = getIndexFieldMap(new String[]{"data."+fieldName}, client, indices); - boolean failure = false; - for (String index : indicesSet) { - if (indexMappingMap.get(index)!=null && updateMappingForAllIndicesOfSameTypeToEnableKeywordIndexingForField(client, index, indexMappingMap.get(index), fieldName)) { - log.info(String.format("Updated field: %s | index: %s", fieldName, index)); - } else { - failure=true; - log.warning(String.format("Failed to update field: %s | index %s", fieldName, index)); - } - } - return !failure; - } - - private Map<String, Map<String, Map<String, FieldMappingMetaData>>> getIndexFieldMap(String[] fieldNames, RestHighLevelClient client, String[] indices) throws IOException { - Map<String, Map<String, Map<String, FieldMappingMetaData>>> indexMappingMap = new HashMap<>(); - GetFieldMappingsRequest request = new GetFieldMappingsRequest(); - request.indices(indices); - request.fields(fieldNames); - try { - GetFieldMappingsResponse response = client.indices().getFieldMapping(request, RequestOptions.DEFAULT); - if (response != null && !response.mappings().isEmpty()) { - final Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings = response.mappings(); - for (String index : indices) { - //extract mapping of each index - final Map<String, Map<String, FieldMappingMetaData>> indexMapping = mappings.get(index); - if (indexMapping != null && !indexMapping.isEmpty()) { - indexMappingMap.put(index, indexMapping); - } - } - } - - return indexMappingMap; - } catch (ElasticsearchException e) { - log.error(String.format("Failed to get indices: %s. | Error: %s", Arrays.toString(indices), e)); - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error getting indices.", String.format("Failed to get indices error: %s", Arrays.toString(indices))); - } - } - - private boolean updateMappingForAllIndicesOfSameTypeToEnableKeywordIndexingForField(RestHighLevelClient client, String index, Map<String, Map<String, FieldMappingMetaData>> indexMapping, String fieldName) throws IOException { - PutMappingRequest request = new PutMappingRequest(index); - String type = indexMapping.keySet().iterator().next(); - if(type.isEmpty()) { - log.error(String.format("Could not find type of the mappings for index: %s.", index)); - return false; - } - - request.type(type); - request.timeout(REQUEST_TIMEOUT); - Map<String, FieldMappingMetaData> metaData = indexMapping.get(type); - if(metaData==null || metaData.get("data." + fieldName)==null) { - log.error(String.format("Could not find field: %s in the mapping of index: %s.", fieldName, index)); - return false; - } - - FieldMappingMetaData fieldMetaData = metaData.get("data." + fieldName); - Map<String, Object> source = fieldMetaData.sourceAsMap(); - if(!source.containsKey(fieldName)){ - log.error(String.format("Could not find field: %s in the mapping of index: %s.", fieldName, index)); - return false; - } - - //Index the field with additional keyword type - Map<String, Object> keywordMap = new HashMap<>(); - keywordMap.put(Constants.TYPE, "keyword"); - Map<String, Object> fieldIndexTypeMap = new HashMap<>(); - fieldIndexTypeMap.put("keyword", keywordMap); - Map<String, Object> dataFieldMap = (Map<String, Object>) source.get(fieldName); - dataFieldMap.put("fields", fieldIndexTypeMap); - Map<String, Object> dataProperties = new HashMap<>(); - dataProperties.put(fieldName, dataFieldMap); - Map<String, Object> mapping = new HashMap<>(); - mapping.put(Constants.PROPERTIES, dataProperties); - Map<String, Object> data = new HashMap<>(); - data.put(Constants.DATA,mapping); - Map<String, Object> properties = new HashMap<>(); - properties.put(Constants.PROPERTIES, data); - - request.source(new Gson().toJson(properties), XContentType.JSON); - - try { - AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT); - boolean isIndicesUpdated = updateIndices(client, index); - return response.isAcknowledged() && isIndicesUpdated; - - } catch (Exception e) { - log.error(String.format("Could not update mapping of index: %s. | Error: %s", index, e)); - return false; - } - } - - private boolean updateIndices(RestHighLevelClient client, String index) throws IOException { - UpdateByQueryRequest request = new UpdateByQueryRequest(index); - request.setConflicts("proceed"); - BulkByScrollResponse response = client.updateByQuery(request, RequestOptions.DEFAULT); - if(!response.getBulkFailures().isEmpty()) { - log.error(String.format("Could not update index: %s.",index)); - return false; - } - return true; - } - - /** - * Create a new type in Elasticsearch - * - * @param client Elasticsearch client - * @param index Index name - * @param type Type name - * @param mapping Mapping if any, null if no specific mapping - * @param merge Try to merge mapping if type already exists - * @throws IOException if cannot create index mapping with input json - */ - private void createMappingWithJson(RestHighLevelClient client, String index, String type, String mapping, boolean merge) - throws IOException { - - boolean mappingExist = isTypeExist(client, index, type); - if (merge || !mappingExist) { - createTypeWithMappingInElasticsearch(client, index, type, mapping); - } - } - - /** - * Check if a type already exists - * - * @param client Elasticsearch client - * @param index Index name - * @param type Type name - * @return true if type already exists - * @throws IOException in case Elasticsearch responded with a status code that indicated an error - */ - public boolean isTypeExist(RestHighLevelClient client, String index, String type) throws IOException { - - Request request = new Request("HEAD", "/" + index + "/_mapping/" + type); - Response response = client.getLowLevelClient().performRequest(request); - return response.getStatusLine().getStatusCode() == 200; - } - - /** - * Create a new type in Elasticsearch - * - * @param client Elasticsearch client - * @param index Index name - * @param type Type name - * @param mapping Mapping if any, null if no specific mapping - * @throws IOException if mapping cannot be created - */ - private Boolean createTypeWithMappingInElasticsearch(RestHighLevelClient client, String index, String type, String mapping) throws IOException { - - Preconditions.checkNotNull(client, "client cannot be null"); - Preconditions.checkNotNull(index, "index cannot be null"); - Preconditions.checkNotNull(type, "type cannot be null"); - - try { - if (mapping != null) { - PutMappingRequest request = new PutMappingRequest(index); - request.type(type); - request.source(mapping, XContentType.JSON); - request.timeout(REQUEST_TIMEOUT); - AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT); - return response.isAcknowledged(); - } - } catch (ElasticsearchException e) { - throw new AppException( - e.status().getStatus(), - e.getMessage(), - String.format("Could not create type mapping %s/%s.", index, type), - String.format("Failed creating mapping: %s", mapping), - e); - } - return false; - } +// 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.indexer.service; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.http.HttpStatus; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; +import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; +import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData; +import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.reindex.BulkByScrollResponse; +import org.elasticsearch.index.reindex.UpdateByQueryRequest; + +import com.google.gson.Gson; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.Constants; +import org.opengroup.osdu.core.common.model.indexer.DEAnalyzerType; +import org.opengroup.osdu.core.common.model.indexer.ElasticType; +import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; +import org.opengroup.osdu.core.common.search.Preconditions; +import org.opengroup.osdu.core.common.model.indexer.IndexSchema; +import org.opengroup.osdu.core.common.model.indexer.Records; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.indexer.util.ElasticClientHandler; +import org.springframework.stereotype.Service; +import javax.inject.Inject; + +import static org.opengroup.osdu.core.common.search.Config.isPreDemo; + +@Service +public class IndexerMappingServiceImpl extends MappingServiceImpl implements IndexerMappingService { + + @Inject + private JaxRsDpsLog log; + @Inject + private ElasticClientHandler elasticClientHandler; + private TimeValue REQUEST_TIMEOUT = TimeValue.timeValueMinutes(1); + + + /** + * Create a new type in Elasticsearch + * + * @param client Elasticsearch client + * @param index Index name + * @param merge Try to merge mapping if type already exists + * @throws IOException if cannot create mapping + */ + public String createMapping(RestHighLevelClient client, IndexSchema schema, String index, boolean merge) throws IOException { + + Map<String, Object> mappingMap = this.getIndexMappingFromRecordSchema(schema); + String mapping = new Gson().toJson(mappingMap, Map.class); + this.createMappingWithJson(client, index, schema.getType(), mapping, merge); + return mapping; + } + + /* + * Read schema mapping + * + * @param schema Index schema + * @param type Mapping type + * @return String JSON represnetation of type and elastic type + * + * sample index mapping: + * "properties": { + * all meta attributes + * "acl": { + * "properties": { + mapping of all roles + * } + * }, + * "legal": { + * "properties": { + * mapping of all legal properties + * } + * } + * "data": { + * "properties": { + * all data-source attributes + * } + * } + * } + * */ + public Map<String, Object> getIndexMappingFromRecordSchema(IndexSchema schema) { + + // entire property block + Map<String, Object> properties = new HashMap<>(); + + // meta attribute + Map<String, Object> metaMapping = new HashMap<>(); + for (Map.Entry<String, Object> entry : schema.getMetaSchema().entrySet()) { + String key = entry.getKey(); + if (key.equals(RecordMetaAttribute.ACL.getValue()) || key.equals(RecordMetaAttribute.LEGAL.getValue()) || key.equals(RecordMetaAttribute.ANCESTRY.getValue()) || key.equals(RecordMetaAttribute.INDEX_STATUS.getValue())) { + metaMapping.put(key, entry.getValue()); + } else { + metaMapping.put(key, Records.Type.builder().type(entry.getValue().toString()).build()); + } + } + + // data-source attributes + Map<String, Object> dataMapping = new HashMap<>(); + if (schema.getDataSchema() != null) { + for (Map.Entry<String, String> entry : schema.getDataSchema().entrySet()) { + // Apply de_indexer_analyzer and de_search_analyzer to TEXT field + if (isPreDemo() && ElasticType.TEXT.getValue().equalsIgnoreCase(entry.getValue())) { + log.info(String.format("indexing %s with custom analyzer", entry.getKey())); + dataMapping.put(entry.getKey(), Records.Analyzer.builder().type(entry.getValue()).analyzer(DEAnalyzerType.INDEXER_ANALYZER.getValue()).search_analyzer(DEAnalyzerType.SEARCH_ANALYZER.getValue()).build()); + } else { + dataMapping.put(entry.getKey(), Records.Type.builder().type(entry.getValue()).build()); + } + } + + // inner properties.data.properties block + Map<String, Object> dataProperties = new HashMap<>(); + dataProperties.put(Constants.PROPERTIES, dataMapping); + + // data & meta block + properties.put(Constants.DATA, dataProperties); + } + properties.putAll(metaMapping); + + // entire document properties block + Map<String, Object> documentMapping = new HashMap<>(); + documentMapping.put(Constants.PROPERTIES, properties); + + // don't add dynamic mapping + documentMapping.put("dynamic", false); + + return documentMapping; + } + + @Override + public void updateIndexMappingForIndicesOfSameType(Set<String> indices, String fieldName) throws Exception { + try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { + if(!updateMappingToEnableKeywordIndexingForField(restClient,indices,fieldName)){ + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error updating index mapping.", String.format("Failed to get confirmation from elastic server mapping update for indices: %s", indices)); + } + } + } + + private boolean updateMappingToEnableKeywordIndexingForField(RestHighLevelClient client, Set<String> indicesSet, String fieldName) throws IOException { + String[] indices = indicesSet.toArray(new String[indicesSet.size()]); + Map<String, Map<String, Map<String, FieldMappingMetaData>>> indexMappingMap = getIndexFieldMap(new String[]{"data."+fieldName}, client, indices); + boolean failure = false; + for (String index : indicesSet) { + if (indexMappingMap.get(index)!=null && updateMappingForAllIndicesOfSameTypeToEnableKeywordIndexingForField(client, index, indexMappingMap.get(index), fieldName)) { + log.info(String.format("Updated field: %s | index: %s", fieldName, index)); + } else { + failure=true; + log.warning(String.format("Failed to update field: %s | index %s", fieldName, index)); + } + } + return !failure; + } + + private Map<String, Map<String, Map<String, FieldMappingMetaData>>> getIndexFieldMap(String[] fieldNames, RestHighLevelClient client, String[] indices) throws IOException { + Map<String, Map<String, Map<String, FieldMappingMetaData>>> indexMappingMap = new HashMap<>(); + GetFieldMappingsRequest request = new GetFieldMappingsRequest(); + request.indices(indices); + request.fields(fieldNames); + try { + GetFieldMappingsResponse response = client.indices().getFieldMapping(request, RequestOptions.DEFAULT); + if (response != null && !response.mappings().isEmpty()) { + final Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings = response.mappings(); + for (String index : indices) { + //extract mapping of each index + final Map<String, Map<String, FieldMappingMetaData>> indexMapping = mappings.get(index); + if (indexMapping != null && !indexMapping.isEmpty()) { + indexMappingMap.put(index, indexMapping); + } + } + } + + return indexMappingMap; + } catch (ElasticsearchException e) { + log.error(String.format("Failed to get indices: %s. | Error: %s", Arrays.toString(indices), e)); + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error getting indices.", String.format("Failed to get indices error: %s", Arrays.toString(indices))); + } + } + + private boolean updateMappingForAllIndicesOfSameTypeToEnableKeywordIndexingForField(RestHighLevelClient client, String index, Map<String, Map<String, FieldMappingMetaData>> indexMapping, String fieldName) throws IOException { + PutMappingRequest request = new PutMappingRequest(index); + String type = indexMapping.keySet().iterator().next(); + if(type.isEmpty()) { + log.error(String.format("Could not find type of the mappings for index: %s.", index)); + return false; + } + + request.type(type); + request.timeout(REQUEST_TIMEOUT); + Map<String, FieldMappingMetaData> metaData = indexMapping.get(type); + if(metaData==null || metaData.get("data." + fieldName)==null) { + log.error(String.format("Could not find field: %s in the mapping of index: %s.", fieldName, index)); + return false; + } + + FieldMappingMetaData fieldMetaData = metaData.get("data." + fieldName); + Map<String, Object> source = fieldMetaData.sourceAsMap(); + if(!source.containsKey(fieldName)){ + log.error(String.format("Could not find field: %s in the mapping of index: %s.", fieldName, index)); + return false; + } + + //Index the field with additional keyword type + Map<String, Object> keywordMap = new HashMap<>(); + keywordMap.put(Constants.TYPE, "keyword"); + Map<String, Object> fieldIndexTypeMap = new HashMap<>(); + fieldIndexTypeMap.put("keyword", keywordMap); + Map<String, Object> dataFieldMap = (Map<String, Object>) source.get(fieldName); + dataFieldMap.put("fields", fieldIndexTypeMap); + Map<String, Object> dataProperties = new HashMap<>(); + dataProperties.put(fieldName, dataFieldMap); + Map<String, Object> mapping = new HashMap<>(); + mapping.put(Constants.PROPERTIES, dataProperties); + Map<String, Object> data = new HashMap<>(); + data.put(Constants.DATA,mapping); + Map<String, Object> properties = new HashMap<>(); + properties.put(Constants.PROPERTIES, data); + + request.source(new Gson().toJson(properties), XContentType.JSON); + + try { + AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT); + boolean isIndicesUpdated = updateIndices(client, index); + return response.isAcknowledged() && isIndicesUpdated; + + } catch (Exception e) { + log.error(String.format("Could not update mapping of index: %s. | Error: %s", index, e)); + return false; + } + } + + private boolean updateIndices(RestHighLevelClient client, String index) throws IOException { + UpdateByQueryRequest request = new UpdateByQueryRequest(index); + request.setConflicts("proceed"); + BulkByScrollResponse response = client.updateByQuery(request, RequestOptions.DEFAULT); + if(!response.getBulkFailures().isEmpty()) { + log.error(String.format("Could not update index: %s.",index)); + return false; + } + return true; + } + + /** + * Create a new type in Elasticsearch + * + * @param client Elasticsearch client + * @param index Index name + * @param type Type name + * @param mapping Mapping if any, null if no specific mapping + * @param merge Try to merge mapping if type already exists + * @throws IOException if cannot create index mapping with input json + */ + private void createMappingWithJson(RestHighLevelClient client, String index, String type, String mapping, boolean merge) + throws IOException { + + boolean mappingExist = isTypeExist(client, index, type); + if (merge || !mappingExist) { + createTypeWithMappingInElasticsearch(client, index, type, mapping); + } + } + + /** + * Check if a type already exists + * + * @param client Elasticsearch client + * @param index Index name + * @param type Type name + * @return true if type already exists + * @throws IOException in case Elasticsearch responded with a status code that indicated an error + */ + public boolean isTypeExist(RestHighLevelClient client, String index, String type) throws IOException { + + Request request = new Request("HEAD", "/" + index + "/_mapping/" + type); + Response response = client.getLowLevelClient().performRequest(request); + return response.getStatusLine().getStatusCode() == 200; + } + + /** + * Create a new type in Elasticsearch + * + * @param client Elasticsearch client + * @param index Index name + * @param type Type name + * @param mapping Mapping if any, null if no specific mapping + * @throws IOException if mapping cannot be created + */ + private Boolean createTypeWithMappingInElasticsearch(RestHighLevelClient client, String index, String type, String mapping) throws IOException { + + Preconditions.checkNotNull(client, "client cannot be null"); + Preconditions.checkNotNull(index, "index cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + + try { + if (mapping != null) { + PutMappingRequest request = new PutMappingRequest(index); + request.type(type); + request.source(mapping, XContentType.JSON); + request.timeout(REQUEST_TIMEOUT); + AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT); + return response.isAcknowledged(); + } + } catch (ElasticsearchException e) { + throw new AppException( + e.status().getStatus(), + e.getMessage(), + String.format("Could not create type mapping %s/%s.", index, type), + String.format("Failed creating mapping: %s", mapping), + e); + } + return false; + } } \ No newline at end of file diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java index d346d5c3653b7c7aa9fea7413d2492d944b89298..0fa06829611148c5ed9525dc71a1c4649f995d43 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java @@ -1,583 +1,489 @@ -// 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.indexer.service; - -import com.google.gson.Gson; - -import lombok.extern.java.Log; -import org.apache.http.HttpStatus; -import org.elasticsearch.ElasticsearchStatusException; -import org.elasticsearch.action.bulk.BulkItemResponse; -import org.elasticsearch.action.bulk.BulkRequest; -import org.elasticsearch.action.bulk.BulkResponse; -import org.elasticsearch.action.delete.DeleteRequest; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.rest.RestStatus; -import org.opengroup.osdu.core.common.model.entitlements.Acl; -import org.opengroup.osdu.core.common.Constants; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.indexer.*; -import org.opengroup.osdu.core.common.model.storage.ConversionStatus; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.indexer.provider.interfaces.IPublisher; -import org.opengroup.osdu.indexer.logging.AuditLogger; -import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; -import org.opengroup.osdu.core.common.model.http.RequestStatus; -import org.opengroup.osdu.core.common.model.search.RecordChangedMessages; -import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; -import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; -import org.opengroup.osdu.core.common.search.IndicesService; -import org.opengroup.osdu.indexer.util.ElasticClientHandler; -import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; -import org.apache.commons.beanutils.PropertyUtils; -import org.apache.commons.beanutils.NestedNullException; -import org.springframework.stereotype.Service; - -import javax.inject.Inject; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -@Log -@Service -public class IndexerServiceImpl implements IndexerService { - - private final TimeValue BULK_REQUEST_TIMEOUT = TimeValue.timeValueMinutes(1); - - private static final List<RestStatus> RETRY_ELASTIC_EXCEPTION = new ArrayList<>(Arrays.asList(RestStatus.TOO_MANY_REQUESTS, RestStatus.BAD_GATEWAY, RestStatus.SERVICE_UNAVAILABLE)); - - private final Gson gson = new Gson(); - - @Inject - private JaxRsDpsLog jaxRsDpsLog; - @Inject - private AuditLogger auditLogger; - @Inject - private StorageService storageService; - @Inject - private IndexSchemaService schemaService; - @Inject - private IndicesService indicesService; - @Inject - private IndexerMappingService mappingService; - @Inject - private IPublisher progressPublisher; - @Inject - private ElasticClientHandler elasticClientHandler; - @Inject - private IndexerQueueTaskBuilder indexerQueueTaskBuilder; - @Inject - private ElasticIndexNameResolver elasticIndexNameResolver; - @Inject - private IAttributeParsingService attributeParsingServiceImpl; - @Inject - private IRequestInfo requestInfo; - @Inject - private JobStatus jobStatus; - - private DpsHeaders headers; - - @Override - public JobStatus processRecordChangedMessages(RecordChangedMessages message, List<RecordInfo> recordInfos) throws Exception { - - // this should not happen - if (recordInfos.size() == 0) return null; - - String errorMessage = ""; - List<String> retryRecordIds = new LinkedList<>(); - - // get auth header with service account Authorization - this.headers = this.requestInfo.getHeadersWithDwdAuthZ(); - - // initialize status for all messages. - this.jobStatus.initialize(recordInfos); - - try { - // get upsert records - Map<String, Map<String, OperationType>> upsertRecordMap = RecordInfo.getUpsertRecordIds(recordInfos); - if (upsertRecordMap != null && !upsertRecordMap.isEmpty()) { - List<String> upsertFailureRecordIds = processUpsertRecords(upsertRecordMap); - retryRecordIds.addAll(upsertFailureRecordIds); - } - - // get delete records - Map<String, List<String>> deleteRecordMap = RecordInfo.getDeleteRecordIds(recordInfos); - if (deleteRecordMap != null && !deleteRecordMap.isEmpty()) { - List<String> deleteFailureRecordIds = processDeleteRecords(deleteRecordMap); - retryRecordIds.addAll(deleteFailureRecordIds); - } - - // process schema change messages - Map<String, OperationType> schemaMsgs = RecordInfo.getSchemaMsgs(recordInfos); - if (schemaMsgs != null && !schemaMsgs.isEmpty()) { - this.schemaService.processSchemaMessages(schemaMsgs); - } - - // process failed records - if (retryRecordIds.size() > 0) { - retryAndEnqueueFailedRecords(recordInfos, retryRecordIds, message); - } - } catch (IOException e) { - errorMessage = e.getMessage(); - throw new AppException(HttpStatus.SC_GATEWAY_TIMEOUT, "Internal communication failure", errorMessage, e); - } catch (AppException e) { - errorMessage = e.getMessage(); - throw e; - } catch (Exception e) { - errorMessage = "error indexing records"; - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Unknown error", "An unknown error has occurred.", e); - } finally { - this.jobStatus.finalizeRecordStatus(errorMessage); - this.progressPublisher.publishStatusChangedTagsToTopic(this.headers, jobStatus); - this.updateAuditLog(); - } - - return jobStatus; - } - - private List<String> processUpsertRecords(Map<String, Map<String, OperationType>> upsertRecordMap) throws Exception { - - List<String> failedRecordIds = new LinkedList<>(); - - // get schema for kind - Map<String, IndexSchema> schemas = this.getSchema(upsertRecordMap); - - if (schemas.isEmpty()) return new LinkedList<>(); - - List<String> recordIds = this.jobStatus.getIdsByIndexingStatus(IndexingStatus.PROCESSING); - recordIds.addAll(this.jobStatus.getIdsByIndexingStatus(IndexingStatus.SKIP)); - recordIds.addAll(this.jobStatus.getIdsByIndexingStatus(IndexingStatus.WARN)); - - if (recordIds.isEmpty()) return new LinkedList<>(); - - // get records via storage api - Records storageRecords = this.storageService.getStorageRecords(recordIds); - - // map storage records to indexer payload - RecordIndexerPayload recordIndexerPayload = this.getIndexerPayload(upsertRecordMap, schemas, storageRecords); - - // index records - failedRecordIds.addAll(processElasticMappingAndUpsertRecords(recordIndexerPayload)); - - return failedRecordIds; - } - - private Map<String, IndexSchema> getSchema(Map<String, Map<String, OperationType>> upsertRecordMap) { - - Map<String, IndexSchema> schemas = new HashMap<>(); - - try { - for (Map.Entry<String, Map<String, OperationType>> entry : upsertRecordMap.entrySet()) { - - String kind = entry.getKey(); - IndexSchema schemaObj = this.schemaService.getIndexerInputSchema(kind); - if (schemaObj.isDataSchemaMissing()) { - this.jobStatus.addOrUpdateRecordStatus(entry.getValue().keySet(), IndexingStatus.WARN, HttpStatus.SC_NOT_FOUND, "schema not found", String.format("schema not found | kind: %s", kind)); - } - - schemas.put(kind, schemaObj); - } - } catch (AppException e) { - throw e; - } catch (Exception e) { - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Get schema error", "An error has occurred while getting schema", e); - } - - return schemas; - } - - private RecordIndexerPayload getIndexerPayload(Map<String, Map<String, OperationType>> upsertRecordMap, Map<String, IndexSchema> kindSchemaMap, Records records) { - List<Records.Entity> storageValidRecords=records.getRecords(); - Map<String,List<String>> conversionStatus = getConversionErrors(records.getConversionStatuses()); - List<RecordIndexerPayload.Record> indexerPayload = new ArrayList<>(); - List<IndexSchema> schemas = new ArrayList<>(); - - for (Records.Entity storageRecord : storageValidRecords) { - - Map<String, OperationType> idOperationMap = upsertRecordMap.get(storageRecord.getKind()); - - String recordId=storageRecord.getId(); - if(conversionStatus.get(recordId)!=null){ - for(String status:conversionStatus.get(recordId)) { - jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST,status, String.format("record-id: %s | %s", recordId, status)); - } - } - // skip if storage returned record with same id but different kind - if (idOperationMap == null) { - String message = String.format("storage service returned incorrect record | requested kind: %s | received kind: %s", this.jobStatus.getRecordKindById(storageRecord.getId()), storageRecord.getKind()); - this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.SKIP, RequestStatus.STORAGE_CONFLICT, message, String.format("%s | record-id: %s", message, storageRecord.getId())); - continue; - } - - IndexSchema schema = kindSchemaMap.get(storageRecord.getKind()); - schemas.add(schema); - - // skip indexing of records if data block is empty - RecordIndexerPayload.Record document = prepareIndexerPayload(schema, storageRecord, idOperationMap); - if (document != null) { - indexerPayload.add(document); - } - } - - jaxRsDpsLog.info(String.format("valid upsert records: %s | can be indexed: %s", storageValidRecords.size(), indexerPayload.size())); - - // this should only happen if storage service returned WRONG records with kind for all the records in the messages - if (indexerPayload.isEmpty()) { - throw new AppException(RequestStatus.STORAGE_CONFLICT, "Indexer error", "upsert record failed, storage service returned incorrect records"); - } - - return RecordIndexerPayload.builder().records(indexerPayload).schemas(schemas).build(); - } - - private RecordIndexerPayload.Record prepareIndexerPayload(IndexSchema schemaObj, Records.Entity storageRecord, Map<String, OperationType> idToOperationMap) { - - RecordIndexerPayload.Record document = null; - - try { - Map<String, Object> storageRecordData = storageRecord.getData(); - document = new RecordIndexerPayload.Record(); - if (storageRecordData == null || storageRecordData.isEmpty()) { - String message = "empty or null data block found in the storage record"; - this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.SKIP, HttpStatus.SC_NOT_FOUND, message, String.format("record-id: %s | %s", storageRecord.getId(), message)); - } else if (schemaObj.isDataSchemaMissing()) { - document.setSchemaMissing(true); - } else { - Map<String, Object> dataMap = mapDataPayload(schemaObj, storageRecordData, storageRecord.getId()); - if (dataMap.isEmpty()) { - document.setMappingMismatch(true); - String message = String.format("complete schema mismatch: none of the data attribute can be mapped | data: %s", storageRecordData); - this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.SKIP, HttpStatus.SC_NOT_FOUND, message, String.format("record-id: %s | %s", storageRecord.getId(), message)); - } - document.setData(dataMap); - } - } catch (AppException e) { - this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.FAIL, HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); - jaxRsDpsLog.warning(String.format("record-id: %s | %s", storageRecord.getId(), e.getMessage()), e); - } catch (Exception e) { - this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.FAIL, HttpStatus.SC_INTERNAL_SERVER_ERROR, String.format("error parsing records against schema, error-message: %s", e.getMessage())); - jaxRsDpsLog.error(String.format("record-id: %s | error parsing records against schema, error-message: %s", storageRecord.getId(), e.getMessage()), e); - } - - try { - // index individual parts of kind - String[] kindParts = storageRecord.getKind().split(":"); - - document.setKind(storageRecord.getKind()); - document.setNamespace(kindParts[0] + ":" + kindParts[1]); - document.setType(kindParts[2]); - document.setId(storageRecord.getId()); - document.setVersion(storageRecord.getVersion()); - document.setAcl(storageRecord.getAcl()); - document.setLegal(storageRecord.getLegal()); - RecordStatus recordStatus = this.jobStatus.getJobStatusByRecordId(storageRecord.getId()); - if (recordStatus.getIndexProgress().getStatusCode() == 0) { - recordStatus.getIndexProgress().setStatusCode(HttpStatus.SC_OK); - } - document.setIndexProgress(recordStatus.getIndexProgress()); - if (storageRecord.getAncestry() != null) document.setAncestry(storageRecord.getAncestry()); - document.setOperationType(idToOperationMap.get(storageRecord.getId())); - } catch (Exception e) { - this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.FAIL, HttpStatus.SC_INTERNAL_SERVER_ERROR, String.format("error parsing meta data, error-message: %s", e.getMessage())); - jaxRsDpsLog.error(String.format("record-id: %s | error parsing meta data, error-message: %s", storageRecord.getId(), e.getMessage()), e); - } - return document; - } - - private Map<String, Object> mapDataPayload(IndexSchema schemaObj, Map<String, Object> storageRecordData, String recordId) { - - Map<String, Object> dataMap = new HashMap<>(); - - if (schemaObj.isDataSchemaMissing()) return dataMap; - - // get the key and get the corresponding object from the storageRecord object - for (Map.Entry<String, String> entry : schemaObj.getDataSchema().entrySet()) { - - String name = entry.getKey(); - - Object value = getPropertyValue(recordId, storageRecordData, name); - - if (value == null) continue; - - ElasticType elasticType = ElasticType.forValue(entry.getValue()); - - switch (elasticType) { - case KEYWORD: - case TEXT: - dataMap.put(name, value); - break; - case INTEGER: - this.attributeParsingServiceImpl.tryParseInteger(recordId, name, value, dataMap); - break; - case LONG: - this.attributeParsingServiceImpl.tryParseLong(recordId, name, value, dataMap); - break; - case FLOAT: - this.attributeParsingServiceImpl.tryParseFloat(recordId, name, value, dataMap); - break; - case DOUBLE: - this.attributeParsingServiceImpl.tryParseDouble(recordId, name, value, dataMap); - break; - case BOOLEAN: - this.attributeParsingServiceImpl.tryParseBoolean(recordId, name, value, dataMap); - break; - case DATE: - this.attributeParsingServiceImpl.tryParseDate(recordId, name, value, dataMap); - break; - case GEO_POINT: - this.attributeParsingServiceImpl.tryParseGeopoint(recordId, name, storageRecordData, dataMap); - break; - case GEO_SHAPE: - this.attributeParsingServiceImpl.tryParseGeojson(recordId, name, value, dataMap); - break; - case NESTED: - case OBJECT: - case UNDEFINED: - // don't do anything for now - break; - } - } - - // add these once iterated over the list - schemaObj.getDataSchema().put(IAttributeParsingService.DATA_GEOJSON_TAG, ElasticType.GEO_SHAPE.getValue()); - schemaObj.getDataSchema().remove(IAttributeParsingService.RECORD_GEOJSON_TAG); - - return dataMap; - } - - private List<String> processElasticMappingAndUpsertRecords(RecordIndexerPayload recordIndexerPayload) throws Exception { - - try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { - List<IndexSchema> schemas = recordIndexerPayload.getSchemas(); - if (schemas == null || schemas.isEmpty()) { - return new LinkedList<>(); - } - - // process the schema - this.cacheOrCreateElasticMapping(schemas, restClient); - - // process the records - return this.upsertRecords(recordIndexerPayload.getRecords(), restClient); - } - } - - private void cacheOrCreateElasticMapping(List<IndexSchema> schemas, RestHighLevelClient restClient) throws Exception { - - for (IndexSchema schema : schemas) { - String index = this.elasticIndexNameResolver.getIndexNameFromKind(schema.getKind()); - - // check if index exist - if (this.indicesService.isIndexExist(restClient, index)) continue; - - // create index - Map<String, Object> mapping = this.mappingService.getIndexMappingFromRecordSchema(schema); - if (!this.indicesService.createIndex(restClient, index, null, schema.getType(), mapping)) { - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error creating index.", String.format("Failed to get confirmation from elastic server for index: %s", index)); - } - } - } - - private List<String> upsertRecords(List<RecordIndexerPayload.Record> records, RestHighLevelClient restClient) throws AppException { - if (records == null || records.isEmpty()) return new LinkedList<>(); - - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.timeout(BULK_REQUEST_TIMEOUT); - - for (RecordIndexerPayload.Record record : records) { - if ((record.getData() == null || record.getData().isEmpty()) && !record.skippedDataIndexing()) { - // it will come here when schema is missing - // TODO: rollback once we know what is causing the problem - jaxRsDpsLog.warning(String.format("data not found for record: %s", record)); - } - - OperationType operation = record.getOperationType(); - Map<String, Object> sourceMap = getSourceMap(record); - String index = this.elasticIndexNameResolver.getIndexNameFromKind(record.getKind()); - - if (operation == OperationType.create) { - IndexRequest indexRequest = new IndexRequest(index, record.getType(), record.getId()).source(this.gson.toJson(sourceMap), XContentType.JSON); - bulkRequest.add(indexRequest); - } else if (operation == OperationType.update) { - UpdateRequest updateRequest = new UpdateRequest(index, record.getType(), record.getId()).upsert(this.gson.toJson(sourceMap), XContentType.JSON); - bulkRequest.add(updateRequest); - } - } - - return processBulkRequest(restClient, bulkRequest); - } - - private List<String> processDeleteRecords(Map<String, List<String>> deleteRecordMap) throws Exception { - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.timeout(BULK_REQUEST_TIMEOUT); - - for (Map.Entry<String, List<String>> record : deleteRecordMap.entrySet()) { - - String[] kindParts = record.getKey().split(":"); - String type = kindParts[2]; - - String index = this.elasticIndexNameResolver.getIndexNameFromKind(record.getKey()); - - for (String id : record.getValue()) { - DeleteRequest deleteRequest = new DeleteRequest(index, type, id); - bulkRequest.add(deleteRequest); - } - } - - try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { - return processBulkRequest(restClient, bulkRequest); - } - } - - private List<String> processBulkRequest(RestHighLevelClient restClient, BulkRequest bulkRequest) throws AppException { - - List<String> failureRecordIds = new LinkedList<>(); - if (bulkRequest.numberOfActions() == 0) return failureRecordIds; - - try { - BulkResponse bulkResponse = restClient.bulk(bulkRequest, RequestOptions.DEFAULT); - jaxRsDpsLog.info(String.format("records in bulk request: %s | acknowledged in response: %s", bulkRequest.numberOfActions(), bulkResponse.getItems().length)); - - // log failed bulk requests - ArrayList<String> bulkFailures = new ArrayList<>(); - for (BulkItemResponse bulkItemResponse : bulkResponse.getItems()) { - if (bulkItemResponse.isFailed()) { - BulkItemResponse.Failure failure = bulkItemResponse.getFailure(); - bulkFailures.add(String.format("bulk status: %s id: %s message: %s", failure.getStatus(), failure.getId(), failure.getMessage())); - this.jobStatus.addOrUpdateRecordStatus(bulkItemResponse.getId(), IndexingStatus.FAIL, failure.getStatus().getStatus(), bulkItemResponse.getFailure().getMessage()); - if (RETRY_ELASTIC_EXCEPTION.contains(bulkItemResponse.status())) { - failureRecordIds.add(bulkItemResponse.getId()); - } - } else { - this.jobStatus.addOrUpdateRecordStatus(bulkItemResponse.getId(), IndexingStatus.SUCCESS, HttpStatus.SC_OK, "Indexed Successfully"); - } - } - if (!bulkFailures.isEmpty()) this.jaxRsDpsLog.warning(bulkFailures); - } catch (IOException e) { - // throw explicit 504 for IOException - throw new AppException(HttpStatus.SC_GATEWAY_TIMEOUT, "Elastic error", "Request cannot be completed in specified time.", e); - } catch (ElasticsearchStatusException e) { - throw new AppException(e.status().getStatus(), "Elastic error", e.getMessage(), e); - } catch (Exception e) { - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error indexing records.", e); - } - return failureRecordIds; - } - - private Map<String, Object> getSourceMap(RecordIndexerPayload.Record record) { - - Map<String, Object> indexerPayload = new HashMap<>(); - - // get the key and get the corresponding object from the individualRecord object - if (record.getData() != null) { - Map<String, Object> data = new HashMap<>(); - for (Map.Entry<String, Object> entry : record.getData().entrySet()) { - data.put(entry.getKey(), entry.getValue()); - } - indexerPayload.put(Constants.DATA, data); - } - - indexerPayload.put(RecordMetaAttribute.ID.getValue(), record.getId()); - indexerPayload.put(RecordMetaAttribute.KIND.getValue(), record.getKind()); - indexerPayload.put(RecordMetaAttribute.NAMESPACE.getValue(), record.getNamespace()); - indexerPayload.put(RecordMetaAttribute.TYPE.getValue(), record.getType()); - indexerPayload.put(RecordMetaAttribute.VERSION.getValue(), record.getVersion()); - indexerPayload.put(RecordMetaAttribute.ACL.getValue(), record.getAcl()); - indexerPayload.put(RecordMetaAttribute.X_ACL.getValue(), Acl.flattenAcl(record.getAcl())); - indexerPayload.put(RecordMetaAttribute.LEGAL.getValue(), record.getLegal()); - indexerPayload.put(RecordMetaAttribute.INDEX_STATUS.getValue(), record.getIndexProgress()); - if (record.getAncestry() != null) { - indexerPayload.put(RecordMetaAttribute.ANCESTRY.getValue(), record.getAncestry()); - } - return indexerPayload; - } - - private Object getPropertyValue(String recordId, Map<String, Object> storageRecordData, String propertyKey) { - - try { - // try getting first level property using optimized collection - Object propertyVal = storageRecordData.get(propertyKey); - if (propertyVal != null) return propertyVal; - - // use apache utils to get nested property - return PropertyUtils.getProperty(storageRecordData, propertyKey); - } catch (NestedNullException ignored) { - // property not found in record - } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - jaxRsDpsLog.warning(String.format("record-id: %s | error fetching property: %s | error: %s", recordId, propertyKey, e.getMessage()), e); - } - return null; - } - - private void retryAndEnqueueFailedRecords(List<RecordInfo> recordInfos, List<String> failuresRecordIds, RecordChangedMessages message) throws IOException { - - jaxRsDpsLog.info(String.format("queuing bulk failed records back to task-queue for retry | count: %s | records: %s", failuresRecordIds.size(), failuresRecordIds)); - List<RecordInfo> retryRecordInfos = new LinkedList<>(); - for (String recordId : failuresRecordIds) { - for (RecordInfo origMessage : recordInfos) { - if (origMessage.getId().equalsIgnoreCase(recordId)) { - retryRecordInfos.add(origMessage); - } - } - } - - RecordChangedMessages newMessage = RecordChangedMessages.builder() - .messageId(message.getMessageId()) - .publishTime(message.getPublishTime()) - .data(this.gson.toJson(retryRecordInfos)) - .attributes(message.getAttributes()).build(); - - String payLoad = this.gson.toJson(newMessage); - this.indexerQueueTaskBuilder.createWorkerTask(payLoad, this.headers); - } - - private void updateAuditLog() { - logAuditEvents(OperationType.create, this.auditLogger::indexCreateRecordSuccess, this.auditLogger::indexCreateRecordFail); - logAuditEvents(OperationType.update, this.auditLogger::indexUpdateRecordSuccess, this.auditLogger::indexUpdateRecordFail); - logAuditEvents(OperationType.purge, this.auditLogger::indexPurgeRecordSuccess, this.auditLogger::indexPurgeRecordFail); - logAuditEvents(OperationType.delete, this.auditLogger::indexDeleteRecordSuccess, this.auditLogger::indexDeleteRecordFail); - } - - private void logAuditEvents(OperationType operationType, Consumer<List<String>> successEvent, Consumer<List<String>> failedEvent) { - List<RecordStatus> succeededRecords = this.jobStatus.getRecordStatuses(IndexingStatus.SUCCESS, operationType); - if(!succeededRecords.isEmpty()) { - successEvent.accept(succeededRecords.stream().map(RecordStatus::toString).collect(Collectors.toList())); - } - List<RecordStatus> skippedRecords = this.jobStatus.getRecordStatuses(IndexingStatus.SKIP, operationType); - List<RecordStatus> failedRecords = this.jobStatus.getRecordStatuses(IndexingStatus.FAIL, operationType); - failedRecords.addAll(skippedRecords); - if(!failedRecords.isEmpty()) { - failedEvent.accept(failedRecords.stream().map(RecordStatus::toString).collect(Collectors.toList())); - } - } - - private Map<String,List<String>> getConversionErrors(List<ConversionStatus> conversionStatuses){ - Map<String,List<String>> errorsByRecordId =new HashMap<>(); - for(ConversionStatus conversionStatus:conversionStatuses){ - if(conversionStatus.getStatus().equalsIgnoreCase("ERROR")){ - List<String> statuses=errorsByRecordId.getOrDefault(conversionStatus.getId(),new LinkedList<>()); - statuses.addAll(conversionStatus.getErrors()); - errorsByRecordId.put(conversionStatus.getId(),statuses); - } - } - return errorsByRecordId; - } -} \ 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.indexer.service; + +import com.google.gson.Gson; + +import org.apache.http.HttpStatus; +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.action.bulk.BulkItemResponse; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestStatus; +import org.opengroup.osdu.core.common.model.entitlements.Acl; +import org.opengroup.osdu.core.common.Constants; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.indexer.*; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.indexer.provider.interfaces.IPublisher; +import org.opengroup.osdu.indexer.logging.AuditLogger; +import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; +import org.opengroup.osdu.core.common.model.http.RequestStatus; +import org.opengroup.osdu.core.common.model.search.RecordChangedMessages; +import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; +import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; +import org.opengroup.osdu.core.common.search.IndicesService; +import org.opengroup.osdu.indexer.util.ElasticClientHandler; +import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.beanutils.NestedNullException; +import org.springframework.stereotype.Service; + +import javax.inject.Inject; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Collectors; + +@Service +public class IndexerServiceImpl implements IndexerService { + + private static final TimeValue BULK_REQUEST_TIMEOUT = TimeValue.timeValueMinutes(1); + + private static final List<RestStatus> RETRY_ELASTIC_EXCEPTION = new ArrayList<>(Arrays.asList(RestStatus.TOO_MANY_REQUESTS, RestStatus.BAD_GATEWAY, RestStatus.SERVICE_UNAVAILABLE)); + + private final Gson gson = new Gson(); + + @Inject + private JaxRsDpsLog jaxRsDpsLog; + @Inject + private AuditLogger auditLogger; + @Inject + private StorageService storageService; + @Inject + private IndexSchemaService schemaService; + @Inject + private IndicesService indicesService; + @Inject + private IndexerMappingService mappingService; + @Inject + private IPublisher progressPublisher; + @Inject + private ElasticClientHandler elasticClientHandler; + @Inject + private IndexerQueueTaskBuilder indexerQueueTaskBuilder; + @Inject + private ElasticIndexNameResolver elasticIndexNameResolver; + @Inject + private StorageIndexerPayloadMapper storageIndexerPayloadMapper; + @Inject + private IRequestInfo requestInfo; + @Inject + private JobStatus jobStatus; + + private DpsHeaders headers; + + @Override + public JobStatus processRecordChangedMessages(RecordChangedMessages message, List<RecordInfo> recordInfos) throws Exception { + + // this should not happen + if (recordInfos.size() == 0) return null; + + String errorMessage = ""; + List<String> retryRecordIds = new LinkedList<>(); + + // get auth header with service account Authorization + this.headers = this.requestInfo.getHeadersWithDwdAuthZ(); + + // initialize status for all messages. + this.jobStatus.initialize(recordInfos); + + try { + // get upsert records + Map<String, Map<String, OperationType>> upsertRecordMap = RecordInfo.getUpsertRecordIds(recordInfos); + if (upsertRecordMap != null && !upsertRecordMap.isEmpty()) { + List<String> upsertFailureRecordIds = processUpsertRecords(upsertRecordMap); + retryRecordIds.addAll(upsertFailureRecordIds); + } + + // get delete records + Map<String, List<String>> deleteRecordMap = RecordInfo.getDeleteRecordIds(recordInfos); + if (deleteRecordMap != null && !deleteRecordMap.isEmpty()) { + List<String> deleteFailureRecordIds = processDeleteRecords(deleteRecordMap); + retryRecordIds.addAll(deleteFailureRecordIds); + } + + // process schema change messages + Map<String, OperationType> schemaMsgs = RecordInfo.getSchemaMsgs(recordInfos); + if (schemaMsgs != null && !schemaMsgs.isEmpty()) { + this.schemaService.processSchemaMessages(schemaMsgs); + } + + // process failed records + if (retryRecordIds.size() > 0) { + retryAndEnqueueFailedRecords(recordInfos, retryRecordIds, message); + } + } catch (IOException e) { + errorMessage = e.getMessage(); + throw new AppException(HttpStatus.SC_GATEWAY_TIMEOUT, "Internal communication failure", errorMessage, e); + } catch (AppException e) { + errorMessage = e.getMessage(); + throw e; + } catch (Exception e) { + errorMessage = "error indexing records"; + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Unknown error", "An unknown error has occurred.", e); + } finally { + this.jobStatus.finalizeRecordStatus(errorMessage); + this.updateAuditLog(); + this.progressPublisher.publishStatusChangedTagsToTopic(this.headers, this.jobStatus); + } + + return jobStatus; + } + + private List<String> processUpsertRecords(Map<String, Map<String, OperationType>> upsertRecordMap) throws Exception { + // get schema for kind + Map<String, IndexSchema> schemas = this.getSchema(upsertRecordMap); + + if (schemas.isEmpty()) return new LinkedList<>(); + + // get recordIds with valid upsert index-status + List<String> recordIds = this.jobStatus.getIdsByValidUpsertIndexingStatus(); + + if (recordIds.isEmpty()) return new LinkedList<>(); + + // get records via storage api + Records storageRecords = this.storageService.getStorageRecords(recordIds); + List<String> failedOrRetryRecordIds = new LinkedList<>(storageRecords.getMissingRetryRecords()); + + // map storage records to indexer payload + RecordIndexerPayload recordIndexerPayload = this.getIndexerPayload(upsertRecordMap, schemas, storageRecords); + + jaxRsDpsLog.info(String.format("records change messages received : %s | valid storage bulk records: %s | valid index payload: %s", recordIds.size(), storageRecords.getRecords().size(), recordIndexerPayload.getRecords().size())); + + // index records + failedOrRetryRecordIds.addAll(processElasticMappingAndUpsertRecords(recordIndexerPayload)); + + return failedOrRetryRecordIds; + } + + private Map<String, IndexSchema> getSchema(Map<String, Map<String, OperationType>> upsertRecordMap) { + + Map<String, IndexSchema> schemas = new HashMap<>(); + + try { + for (Map.Entry<String, Map<String, OperationType>> entry : upsertRecordMap.entrySet()) { + + String kind = entry.getKey(); + IndexSchema schemaObj = this.schemaService.getIndexerInputSchema(kind, false); + if (schemaObj.isDataSchemaMissing()) { + this.jobStatus.addOrUpdateRecordStatus(entry.getValue().keySet(), IndexingStatus.WARN, HttpStatus.SC_NOT_FOUND, "schema not found", String.format("schema not found | kind: %s", kind)); + } + + schemas.put(kind, schemaObj); + } + } catch (AppException e) { + throw e; + } catch (Exception e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Get schema error", "An error has occurred while getting schema", e); + } + + return schemas; + } + + private RecordIndexerPayload getIndexerPayload(Map<String, Map<String, OperationType>> upsertRecordMap, Map<String, IndexSchema> kindSchemaMap, Records records) { + List<Records.Entity> storageValidRecords = records.getRecords(); + List<RecordIndexerPayload.Record> indexerPayload = new ArrayList<>(); + List<IndexSchema> schemas = new ArrayList<>(); + + for (Records.Entity storageRecord : storageValidRecords) { + + Map<String, OperationType> idOperationMap = upsertRecordMap.get(storageRecord.getKind()); + + // skip if storage returned record with same id but different kind + if (idOperationMap == null) { + String message = String.format("storage service returned incorrect record | requested kind: %s | received kind: %s", this.jobStatus.getRecordKindById(storageRecord.getId()), storageRecord.getKind()); + this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.SKIP, RequestStatus.STORAGE_CONFLICT, message, String.format("%s | record-id: %s", message, storageRecord.getId())); + continue; + } + + IndexSchema schema = kindSchemaMap.get(storageRecord.getKind()); + schemas.add(schema); + + // skip indexing of records if data block is empty + RecordIndexerPayload.Record document = prepareIndexerPayload(schema, storageRecord, idOperationMap); + if (document != null) { + indexerPayload.add(document); + } + } + + // this should only happen if storage service returned WRONG records with kind for all the records in the messages + if (indexerPayload.isEmpty()) { + throw new AppException(RequestStatus.STORAGE_CONFLICT, "Indexer error", "upsert record failed, storage service returned incorrect records"); + } + + return RecordIndexerPayload.builder().records(indexerPayload).schemas(schemas).build(); + } + + private RecordIndexerPayload.Record prepareIndexerPayload(IndexSchema schemaObj, Records.Entity storageRecord, Map<String, OperationType> idToOperationMap) { + + RecordIndexerPayload.Record document = null; + + try { + Map<String, Object> storageRecordData = storageRecord.getData(); + document = new RecordIndexerPayload.Record(); + if (storageRecordData == null || storageRecordData.isEmpty()) { + String message = "empty or null data block found in the storage record"; + this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.WARN, HttpStatus.SC_NOT_FOUND, message, String.format("record-id: %s | %s", storageRecord.getId(), message)); + } else if (schemaObj.isDataSchemaMissing()) { + document.setSchemaMissing(true); + } else { + Map<String, Object> dataMap = this.storageIndexerPayloadMapper.mapDataPayload(schemaObj, storageRecordData, storageRecord.getId()); + if (dataMap.isEmpty()) { + document.setMappingMismatch(true); + String message = String.format("complete schema mismatch: none of the data attribute can be mapped | data: %s", storageRecordData); + this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.WARN, HttpStatus.SC_NOT_FOUND, message, String.format("record-id: %s | %s", storageRecord.getId(), message)); + } + document.setData(dataMap); + } + } catch (AppException e) { + this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.FAIL, HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + jaxRsDpsLog.warning(String.format("record-id: %s | %s", storageRecord.getId(), e.getMessage()), e); + } catch (Exception e) { + this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.FAIL, HttpStatus.SC_INTERNAL_SERVER_ERROR, String.format("error parsing records against schema, error-message: %s", e.getMessage())); + jaxRsDpsLog.error(String.format("record-id: %s | error parsing records against schema, error-message: %s", storageRecord.getId(), e.getMessage()), e); + } + + try { + // index individual parts of kind + String[] kindParts = storageRecord.getKind().split(":"); + + document.setKind(storageRecord.getKind()); + document.setNamespace(kindParts[0] + ":" + kindParts[1]); + document.setType(kindParts[2]); + document.setId(storageRecord.getId()); + document.setVersion(storageRecord.getVersion()); + document.setAcl(storageRecord.getAcl()); + document.setLegal(storageRecord.getLegal()); + RecordStatus recordStatus = this.jobStatus.getJobStatusByRecordId(storageRecord.getId()); + if (recordStatus.getIndexProgress().getStatusCode() == 0) { + recordStatus.getIndexProgress().setStatusCode(HttpStatus.SC_OK); + } + document.setIndexProgress(recordStatus.getIndexProgress()); + if (storageRecord.getAncestry() != null) document.setAncestry(storageRecord.getAncestry()); + document.setOperationType(idToOperationMap.get(storageRecord.getId())); + } catch (Exception e) { + this.jobStatus.addOrUpdateRecordStatus(storageRecord.getId(), IndexingStatus.FAIL, HttpStatus.SC_INTERNAL_SERVER_ERROR, String.format("error parsing meta data, error-message: %s", e.getMessage())); + jaxRsDpsLog.error(String.format("record-id: %s | error parsing meta data, error-message: %s", storageRecord.getId(), e.getMessage()), e); + } + return document; + } + + private List<String> processElasticMappingAndUpsertRecords(RecordIndexerPayload recordIndexerPayload) throws Exception { + + try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { + List<IndexSchema> schemas = recordIndexerPayload.getSchemas(); + if (schemas == null || schemas.isEmpty()) { + return new LinkedList<>(); + } + + // process the schema + this.cacheOrCreateElasticMapping(schemas, restClient); + + // process the records + return this.upsertRecords(recordIndexerPayload.getRecords(), restClient); + } + } + + private void cacheOrCreateElasticMapping(List<IndexSchema> schemas, RestHighLevelClient restClient) throws Exception { + + for (IndexSchema schema : schemas) { + String index = this.elasticIndexNameResolver.getIndexNameFromKind(schema.getKind()); + + // check if index exist + if (this.indicesService.isIndexExist(restClient, index)) continue; + + // create index + Map<String, Object> mapping = this.mappingService.getIndexMappingFromRecordSchema(schema); + if (!this.indicesService.createIndex(restClient, index, null, schema.getType(), mapping)) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error creating index.", String.format("Failed to get confirmation from elastic server for index: %s", index)); + } + } + } + + private List<String> upsertRecords(List<RecordIndexerPayload.Record> records, RestHighLevelClient restClient) throws AppException { + if (records == null || records.isEmpty()) return new LinkedList<>(); + + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.timeout(BULK_REQUEST_TIMEOUT); + + for (RecordIndexerPayload.Record record : records) { + if ((record.getData() == null || record.getData().isEmpty()) && !record.skippedDataIndexing()) { + // it will come here when schema is missing + // TODO: rollback once we know what is causing the problem + jaxRsDpsLog.warning(String.format("data not found for record: %s", record)); + } + + OperationType operation = record.getOperationType(); + Map<String, Object> sourceMap = getSourceMap(record); + String index = this.elasticIndexNameResolver.getIndexNameFromKind(record.getKind()); + + if (operation == OperationType.create) { + IndexRequest indexRequest = new IndexRequest(index, record.getType(), record.getId()).source(this.gson.toJson(sourceMap), XContentType.JSON); + bulkRequest.add(indexRequest); + } else if (operation == OperationType.update) { + UpdateRequest updateRequest = new UpdateRequest(index, record.getType(), record.getId()).upsert(this.gson.toJson(sourceMap), XContentType.JSON); + bulkRequest.add(updateRequest); + } + } + + return processBulkRequest(restClient, bulkRequest); + } + + private List<String> processDeleteRecords(Map<String, List<String>> deleteRecordMap) throws Exception { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.timeout(BULK_REQUEST_TIMEOUT); + + for (Map.Entry<String, List<String>> record : deleteRecordMap.entrySet()) { + + String[] kindParts = record.getKey().split(":"); + String type = kindParts[2]; + + String index = this.elasticIndexNameResolver.getIndexNameFromKind(record.getKey()); + + for (String id : record.getValue()) { + DeleteRequest deleteRequest = new DeleteRequest(index, type, id); + bulkRequest.add(deleteRequest); + } + } + + try (RestHighLevelClient restClient = this.elasticClientHandler.createRestClient()) { + return processBulkRequest(restClient, bulkRequest); + } + } + + private List<String> processBulkRequest(RestHighLevelClient restClient, BulkRequest bulkRequest) throws AppException { + + List<String> failureRecordIds = new LinkedList<>(); + if (bulkRequest.numberOfActions() == 0) return failureRecordIds; + + + + try { + BulkResponse bulkResponse = restClient.bulk(bulkRequest, RequestOptions.DEFAULT); + + // log failed bulk requests + ArrayList<String> bulkFailures = new ArrayList<>(); + int succeededResponses = 0; + int failedResponses = 0; + + for (BulkItemResponse bulkItemResponse : bulkResponse.getItems()) { + if (bulkItemResponse.isFailed()) { + BulkItemResponse.Failure failure = bulkItemResponse.getFailure(); + bulkFailures.add(String.format("elasticsearch bulk service status: %s id: %s message: %s", failure.getStatus(), failure.getId(), failure.getMessage())); + this.jobStatus.addOrUpdateRecordStatus(bulkItemResponse.getId(), IndexingStatus.FAIL, failure.getStatus().getStatus(), bulkItemResponse.getFailureMessage()); + if (RETRY_ELASTIC_EXCEPTION.contains(bulkItemResponse.status())) { + failureRecordIds.add(bulkItemResponse.getId()); + } + failedResponses++; + } else { + succeededResponses++; + this.jobStatus.addOrUpdateRecordStatus(bulkItemResponse.getId(), IndexingStatus.SUCCESS, HttpStatus.SC_OK, "Indexed Successfully"); + } + } + if (!bulkFailures.isEmpty()) this.jaxRsDpsLog.warning(bulkFailures); + + jaxRsDpsLog.info(String.format("records in elasticsearch service bulk request: %s | successful: %s | failed: %s", bulkRequest.numberOfActions(), succeededResponses, failedResponses)); + } catch (IOException e) { + // throw explicit 504 for IOException + throw new AppException(HttpStatus.SC_GATEWAY_TIMEOUT, "Elastic error", "Request cannot be completed in specified time.", e); + } catch (ElasticsearchStatusException e) { + throw new AppException(e.status().getStatus(), "Elastic error", e.getMessage(), e); + } catch (Exception e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Elastic error", "Error indexing records.", e); + } + return failureRecordIds; + } + + private Map<String, Object> getSourceMap(RecordIndexerPayload.Record record) { + + Map<String, Object> indexerPayload = new HashMap<>(); + + // get the key and get the corresponding object from the individualRecord object + if (record.getData() != null) { + Map<String, Object> data = new HashMap<>(); + for (Map.Entry<String, Object> entry : record.getData().entrySet()) { + data.put(entry.getKey(), entry.getValue()); + } + indexerPayload.put(Constants.DATA, data); + } + + indexerPayload.put(RecordMetaAttribute.ID.getValue(), record.getId()); + indexerPayload.put(RecordMetaAttribute.KIND.getValue(), record.getKind()); + indexerPayload.put(RecordMetaAttribute.NAMESPACE.getValue(), record.getNamespace()); + indexerPayload.put(RecordMetaAttribute.TYPE.getValue(), record.getType()); + indexerPayload.put(RecordMetaAttribute.VERSION.getValue(), record.getVersion()); + indexerPayload.put(RecordMetaAttribute.ACL.getValue(), record.getAcl()); + indexerPayload.put(RecordMetaAttribute.X_ACL.getValue(), Acl.flattenAcl(record.getAcl())); + indexerPayload.put(RecordMetaAttribute.LEGAL.getValue(), record.getLegal()); + indexerPayload.put(RecordMetaAttribute.INDEX_STATUS.getValue(), record.getIndexProgress()); + if (record.getAncestry() != null) { + indexerPayload.put(RecordMetaAttribute.ANCESTRY.getValue(), record.getAncestry()); + } + return indexerPayload; + } + + private void retryAndEnqueueFailedRecords(List<RecordInfo> recordInfos, List<String> failuresRecordIds, RecordChangedMessages message) throws IOException { + + jaxRsDpsLog.info(String.format("queuing bulk failed records back to task-queue for retry | count: %s | records: %s", failuresRecordIds.size(), failuresRecordIds)); + List<RecordInfo> retryRecordInfos = new LinkedList<>(); + for (String recordId : failuresRecordIds) { + for (RecordInfo origMessage : recordInfos) { + if (origMessage.getId().equalsIgnoreCase(recordId)) { + retryRecordInfos.add(origMessage); + } + } + } + + RecordChangedMessages newMessage = RecordChangedMessages.builder() + .messageId(message.getMessageId()) + .publishTime(message.getPublishTime()) + .data(this.gson.toJson(retryRecordInfos)) + .attributes(message.getAttributes()).build(); + + String payLoad = this.gson.toJson(newMessage); + this.indexerQueueTaskBuilder.createWorkerTask(payLoad, this.headers); + } + + private void updateAuditLog() { + logAuditEvents(OperationType.create, this.auditLogger::indexCreateRecordSuccess, this.auditLogger::indexCreateRecordFail); + logAuditEvents(OperationType.update, this.auditLogger::indexUpdateRecordSuccess, this.auditLogger::indexUpdateRecordFail); + logAuditEvents(OperationType.purge, this.auditLogger::indexPurgeRecordSuccess, this.auditLogger::indexPurgeRecordFail); + logAuditEvents(OperationType.delete, this.auditLogger::indexDeleteRecordSuccess, this.auditLogger::indexDeleteRecordFail); + } + + private void logAuditEvents(OperationType operationType, Consumer<List<String>> successEvent, Consumer<List<String>> failedEvent) { + List<RecordStatus> succeededRecords = this.jobStatus.getRecordStatuses(IndexingStatus.SUCCESS, operationType); + if(!succeededRecords.isEmpty()) { + successEvent.accept(succeededRecords.stream().map(RecordStatus::succeededAuditLogMessage).collect(Collectors.toList())); + } + List<RecordStatus> skippedRecords = this.jobStatus.getRecordStatuses(IndexingStatus.SKIP, operationType); + List<RecordStatus> failedRecords = this.jobStatus.getRecordStatuses(IndexingStatus.FAIL, operationType); + failedRecords.addAll(skippedRecords); + if(!failedRecords.isEmpty()) { + failedEvent.accept(failedRecords.stream().map(RecordStatus::failedAuditLogMessage).collect(Collectors.toList())); + } + } +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java index 22374fc32075455e31df5dd9e4b912f34d9d3292..0f13dde455b6eb97b8cef6de07d4fde14fbd77cb 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndicesServiceImpl.java @@ -151,7 +151,7 @@ public class IndicesServiceImpl implements IndicesService { * @param client Elasticsearch client * @param index Index name */ - public boolean deleteIndex(RestHighLevelClient client, String index) throws Exception { + public boolean deleteIndex(RestHighLevelClient client, String index) throws ElasticsearchException, IOException, AppException { boolean responseStatus = removeIndexInElasticsearch(client, index); if (responseStatus) { this.indicesExistCache.delete(index); @@ -164,7 +164,7 @@ public class IndicesServiceImpl implements IndicesService { * * @param index Index name */ - public boolean deleteIndex(String index) throws Exception { + public boolean deleteIndex(String index) throws ElasticsearchException, IOException, AppException { try (RestHighLevelClient client = this.elasticClientHandler.createRestClient()) { return deleteIndex(client, index); } @@ -177,7 +177,7 @@ public class IndicesServiceImpl implements IndicesService { * @param index Index name * @throws Exception Throws {@link AppException} if index is not found or elastic cannot delete the index */ - private boolean removeIndexInElasticsearch(RestHighLevelClient client, String index) throws Exception { + private boolean removeIndexInElasticsearch(RestHighLevelClient client, String index) throws ElasticsearchException, IOException, AppException { Preconditions.checkArgument(client, Objects::nonNull, "client cannot be null"); Preconditions.checkArgument(index, Objects::nonNull, "index cannot be null"); diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexService.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexService.java index 40f22aaa26f931f6bde8aa8d0d28f33a61cbc16e..39486f1c7498260e7eda5fd83b9de379f79b1d4f 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexService.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexService.java @@ -19,5 +19,5 @@ import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; public interface ReindexService { - String reindexRecords(RecordReindexRequest recordReindexRequest); + String reindexRecords(RecordReindexRequest recordReindexRequest, boolean forceClean); } \ No newline at end of file diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexServiceImpl.java index 16a8c0d03d41e2566283c0528adfc87d4001d682..ac471b3946609ec01facd3d23b926140a15a2e4e 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/ReindexServiceImpl.java @@ -16,7 +16,6 @@ package org.opengroup.osdu.indexer.service; import com.google.common.base.Strings; import com.google.gson.Gson; -import lombok.extern.java.Log; import org.apache.http.HttpStatus; import org.opengroup.osdu.core.common.model.http.DpsHeaders; import org.opengroup.osdu.core.common.model.http.AppException; @@ -24,6 +23,7 @@ import org.opengroup.osdu.core.common.model.indexer.OperationType; import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.search.Config; import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; import org.opengroup.osdu.core.common.model.indexer.RecordInfo; import org.opengroup.osdu.core.common.model.search.RecordChangedMessages; @@ -36,7 +36,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -@Log @Component public class ReindexServiceImpl implements ReindexService { @@ -47,14 +46,22 @@ public class ReindexServiceImpl implements ReindexService { @Inject private IRequestInfo requestInfo; @Inject + private IndexSchemaService indexSchemaService; + @Inject private JaxRsDpsLog jaxRsDpsLog; @Override - public String reindexRecords(RecordReindexRequest recordReindexRequest) { + public String reindexRecords(RecordReindexRequest recordReindexRequest, boolean forceClean) { + Long initialDelayMillis = 0l; try { DpsHeaders headers = this.requestInfo.getHeadersWithDwdAuthZ(); + if (forceClean) { + this.indexSchemaService.syncIndexMappingWithStorageSchema(recordReindexRequest.getKind()); + initialDelayMillis = 30000l; + } + RecordQueryResponse recordQueryResponse = this.storageService.getRecordsByKind(recordReindexRequest); if (recordQueryResponse.getResults() != null && recordQueryResponse.getResults().size() != 0) { @@ -70,11 +77,13 @@ public class ReindexServiceImpl implements ReindexService { Gson gson = new Gson(); RecordChangedMessages recordChangedMessages = RecordChangedMessages.builder().data(gson.toJson(msgs)).attributes(attributes).build(); String recordChangedMessagePayload = gson.toJson(recordChangedMessages); - this.indexerQueueTaskBuilder.createWorkerTask(recordChangedMessagePayload, headers); + this.indexerQueueTaskBuilder.createWorkerTask(recordChangedMessagePayload, initialDelayMillis, headers); - if (!Strings.isNullOrEmpty(recordQueryResponse.getCursor())) { + // don't call reindex-worker endpoint if it's the last batch + // previous storage query result size will be less then requested (limit param) + if (!Strings.isNullOrEmpty(recordQueryResponse.getCursor()) && recordQueryResponse.getResults().size() == Config.getStorageRecordsBatchSize()) { String newPayLoad = gson.toJson(RecordReindexRequest.builder().cursor(recordQueryResponse.getCursor()).kind(recordReindexRequest.getKind()).build()); - this.indexerQueueTaskBuilder.createReIndexTask(newPayLoad, headers); + this.indexerQueueTaskBuilder.createReIndexTask(newPayLoad, initialDelayMillis, headers); return newPayLoad; } diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageIndexerPayloadMapper.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageIndexerPayloadMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..bef023703b60341eb2c214740e1acb1ef23243df --- /dev/null +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageIndexerPayloadMapper.java @@ -0,0 +1,124 @@ +package org.opengroup.osdu.indexer.service; + +import org.apache.commons.beanutils.NestedNullException; +import org.apache.commons.beanutils.PropertyUtils; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.indexer.ElasticType; +import org.opengroup.osdu.core.common.model.indexer.IndexSchema; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.lang.reflect.InvocationTargetException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.opengroup.osdu.indexer.service.IAttributeParsingService.DATA_GEOJSON_TAG; +import static org.opengroup.osdu.indexer.service.IAttributeParsingService.RECORD_GEOJSON_TAG; + +@Component +public class StorageIndexerPayloadMapper { + + @Inject + private JaxRsDpsLog log; + @Inject + private IAttributeParsingService attributeParsingService; + + public Map<String, Object> mapDataPayload(IndexSchema storageSchema, Map<String, Object> storageRecordData, String recordId) { + + Map<String, Object> dataMap = new HashMap<>(); + + if (storageSchema.isDataSchemaMissing()) return dataMap; + + // get the key and get the corresponding object from the storageRecord object + for (Map.Entry<String, String> entry : storageSchema.getDataSchema().entrySet()) { + + String name = entry.getKey(); + + Object value = getPropertyValue(recordId, storageRecordData, name); + + if (value == null) continue; + + ElasticType elasticType = ElasticType.forValue(entry.getValue()); + + switch (elasticType) { + case KEYWORD: + case KEYWORD_ARRAY: + case TEXT: + case TEXT_ARRAY: + dataMap.put(name, value); + break; + case INTEGER_ARRAY: + this.attributeParsingService.tryParseValueArray(Integer.class, recordId, name, value, dataMap); + break; + case INTEGER: + this.attributeParsingService.tryParseInteger(recordId, name, value, dataMap); + break; + case LONG_ARRAY: + this.attributeParsingService.tryParseValueArray(Long.class, recordId, name, value, dataMap); + break; + case LONG: + this.attributeParsingService.tryParseLong(recordId, name, value, dataMap); + break; + case FLOAT_ARRAY: + this.attributeParsingService.tryParseValueArray(Float.class, recordId, name, value, dataMap); + break; + case FLOAT: + this.attributeParsingService.tryParseFloat(recordId, name, value, dataMap); + break; + case DOUBLE_ARRAY: + this.attributeParsingService.tryParseValueArray(Double.class, recordId, name, value, dataMap); + break; + case DOUBLE: + this.attributeParsingService.tryParseDouble(recordId, name, value, dataMap); + break; + case BOOLEAN_ARRAY: + this.attributeParsingService.tryParseValueArray(Boolean.class, recordId, name, value, dataMap); + break; + case BOOLEAN: + this.attributeParsingService.tryParseBoolean(recordId, name, value, dataMap); + break; + case DATE_ARRAY: + this.attributeParsingService.tryParseValueArray(Date.class, recordId, name, value, dataMap); + break; + case DATE: + this.attributeParsingService.tryParseDate(recordId, name, value, dataMap); + break; + case GEO_POINT: + this.attributeParsingService.tryParseGeopoint(recordId, name, storageRecordData, dataMap); + break; + case GEO_SHAPE: + this.attributeParsingService.tryParseGeojson(recordId, name, value, dataMap); + break; + case NESTED: + case OBJECT: + case UNDEFINED: + // don't do anything for now + break; + } + } + + // add these once iterated over the list + storageSchema.getDataSchema().put(DATA_GEOJSON_TAG, ElasticType.GEO_SHAPE.getValue()); + storageSchema.getDataSchema().remove(RECORD_GEOJSON_TAG); + + return dataMap; + } + + private Object getPropertyValue(String recordId, Map<String, Object> storageRecordData, String propertyKey) { + + try { + // try getting first level property using optimized collection + Object propertyVal = storageRecordData.get(propertyKey); + if (propertyVal != null) return propertyVal; + + // use apache utils to get nested property + return PropertyUtils.getProperty(storageRecordData, propertyKey); + } catch (NestedNullException ignored) { + // property not found in record + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + this.log.warning(String.format("record-id: %s | error fetching property: %s | error: %s", recordId, propertyKey, e.getMessage()), e); + } + return null; + } +} \ No newline at end of file diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageServiceImpl.java index ec0fbd948171d8c0990dd60403be5a652a5f1761..4fb392ded7f7900b1df3b966c207506ab56072d2 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageServiceImpl.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageServiceImpl.java @@ -1,153 +1,224 @@ -// 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.indexer.service; - -import com.google.api.client.http.HttpMethods; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; -import lombok.extern.java.Log; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.http.HttpResponse; -import org.opengroup.osdu.core.common.model.http.RequestStatus; -import org.opengroup.osdu.core.common.model.indexer.*; -import org.opengroup.osdu.core.common.model.storage.ConversionStatus; -import org.opengroup.osdu.core.common.model.storage.RecordIds; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.core.common.http.IUrlFetchService; -import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; -import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; -import org.apache.http.HttpStatus; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.inject.Inject; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Type; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.opengroup.osdu.core.common.model.http.DpsHeaders.FRAME_OF_REFERENCE; -import static org.opengroup.osdu.core.common.Constants.SLB_FRAME_OF_REFERENCE_VALUE; - -@Log -@Component -public class StorageServiceImpl implements StorageService { - - private final Gson gson = new Gson(); - - @Inject - private IUrlFetchService urlFetchService; - @Inject - private JobStatus jobStatus; - @Inject - private IRequestInfo requestInfo; - @Inject - private JaxRsDpsLog jaxRsDpsLog; - - @Value("${STORAGE_SCHEMA_HOST}") - private String STORAGE_SCHEMA_HOST; - - @Value("${STORAGE_QUERY_RECORD_HOST}") - private String STORAGE_QUERY_RECORD_HOST; - - @Value("${STORAGE_QUERY_RECORD_FOR_CONVERSION_HOST}") - private String STORAGE_QUERY_RECORD_FOR_CONVERSION_HOST; - - @Value("${STORAGE_RECORDS_BATCH_SIZE}") - private String STORAGE_RECORDS_BATCH_SIZE; - - @Override - public Records getStorageRecords(List<String> ids) throws AppException, URISyntaxException { - List<Records.Entity> valid = new ArrayList<>(); - List<String> notFound = new ArrayList<>(); - List<ConversionStatus> conversionStatuses = new ArrayList<>(); - - List<List<String>> batch = Lists.partition(ids, Integer.parseInt(STORAGE_RECORDS_BATCH_SIZE)); - for (List<String> recordsBatch : batch) { - Records storageOut = this.getRecords(recordsBatch); - valid.addAll(storageOut.getRecords()); - notFound.addAll(storageOut.getNotFound()); - conversionStatuses.addAll(storageOut.getConversionStatuses()); - } - return Records.builder().records(valid).notFound(notFound).conversionStatuses(conversionStatuses).build(); - } - - private Records getRecords(List<String> ids) throws URISyntaxException { - // e.g. {"records":["test:10"]} - String body = this.gson.toJson(RecordIds.builder().records(ids).build()); - -// Map<String, String> headers = this.requestInfo.getHeadersMap(); - DpsHeaders headers = this.requestInfo.getHeaders(); - headers.put(FRAME_OF_REFERENCE, SLB_FRAME_OF_REFERENCE_VALUE); - HttpResponse response = this.urlFetchService.sendRequest(HttpMethods.POST, STORAGE_QUERY_RECORD_FOR_CONVERSION_HOST, headers, null, body); - String dataFromStorage = response.getBody(); - if (Strings.isNullOrEmpty(dataFromStorage)) { - throw new AppException(HttpStatus.SC_NOT_FOUND, "Invalid request", "Storage service cannot locate records"); - } - - Type recordsListType = new TypeToken<Records>() {}.getType(); - Records records = this.gson.fromJson(dataFromStorage, recordsListType); - - // update status for invalid records from storage - if (records.getNotFound() != null && !records.getNotFound().isEmpty()) { - this.jobStatus.addOrUpdateRecordStatus(records.getNotFound(), IndexingStatus.FAIL, RequestStatus.INVALID_RECORD, "invalid storage records", String.format("invalid records: %s", String.join(",", records.getNotFound()))); - } - - // don't proceed if there is nothing to process - List<Records.Entity> validRecords = records.getRecords(); - if (validRecords == null || validRecords.isEmpty()) { - if (response.isSuccessCode()) { - throw new AppException(RequestStatus.INVALID_RECORD, "Invalid request", "Storage service returned retry or invalid records"); - } - - // TODO: returned actual code from storage service - jaxRsDpsLog.warning(String.format("unable to proceed, valid storage record not found. | upstream response code: %s | record ids: %s", response.getResponseCode(), String.join(" | ", ids))); - throw new AppException(HttpStatus.SC_NOT_FOUND, "Invalid request", "Storage service cannot locate valid records"); - } - - return records; - } - - @Override - public RecordQueryResponse getRecordsByKind(RecordReindexRequest request) throws URISyntaxException { - Map<String, String> queryParams = new HashMap<>(); - queryParams.put(RecordMetaAttribute.KIND.getValue(), request.getKind()); - queryParams.put("limit", STORAGE_RECORDS_BATCH_SIZE); - if (!Strings.isNullOrEmpty(request.getCursor())) { - queryParams.put("cursor", request.getCursor()); - } - - if(requestInfo == null) - throw new AppException(HttpStatus.SC_NO_CONTENT, "Invalid header", "header can't be null"); - - HttpResponse response = this.urlFetchService.sendRequest(HttpMethods.GET, STORAGE_QUERY_RECORD_HOST, this.requestInfo.getHeaders(), queryParams, null); - return this.gson.fromJson(response.getBody(), RecordQueryResponse.class); - } - - @Override - public String getStorageSchema(String kind) throws URISyntaxException, UnsupportedEncodingException { - String url = String.format("%s/%s", STORAGE_SCHEMA_HOST, URLEncoder.encode(kind, "UTF-8")); - HttpResponse response = this.urlFetchService.sendRequest(HttpMethods.GET, url, this.requestInfo.getHeaders(), null, null); - if (response.getResponseCode() != HttpStatus.SC_OK) return null; - return response.getBody(); - } +// 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.indexer.service; + +import com.google.api.client.http.HttpMethods; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; + +import org.opengroup.osdu.core.common.http.FetchServiceHttpRequest; +import lombok.extern.java.Log; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.http.HttpResponse; +import org.opengroup.osdu.core.common.model.http.RequestStatus; +import org.opengroup.osdu.core.common.model.indexer.*; +import org.opengroup.osdu.core.common.model.storage.ConversionStatus; +import org.opengroup.osdu.core.common.model.storage.RecordIds; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.http.IUrlFetchService; +import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; +import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; +import org.apache.http.HttpStatus; +import org.opengroup.osdu.core.common.search.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.opengroup.osdu.core.common.model.http.DpsHeaders.FRAME_OF_REFERENCE; +import static org.opengroup.osdu.core.common.Constants.SLB_FRAME_OF_REFERENCE_VALUE; + +@Component +public class StorageServiceImpl implements StorageService { + + private final Gson gson = new Gson(); + + @Inject + private IUrlFetchService urlFetchService; + @Inject + private JobStatus jobStatus; + @Inject + private IRequestInfo requestInfo; + @Inject + private JaxRsDpsLog jaxRsDpsLog; + + @Value("${STORAGE_SCHEMA_HOST}") + private String STORAGE_SCHEMA_HOST; + + @Value("${STORAGE_QUERY_RECORD_HOST}") + private String STORAGE_QUERY_RECORD_HOST; + + @Value("${STORAGE_QUERY_RECORD_FOR_CONVERSION_HOST}") + private String STORAGE_QUERY_RECORD_FOR_CONVERSION_HOST; + + @Value("${STORAGE_RECORDS_BATCH_SIZE}") + private String STORAGE_RECORDS_BATCH_SIZE; + + @Override + public Records getStorageRecords(List<String> ids) throws AppException, URISyntaxException { + List<Records.Entity> valid = new ArrayList<>(); + List<String> notFound = new ArrayList<>(); + List<ConversionStatus> conversionStatuses = new ArrayList<>(); + List<String> missingRetryRecordIds = new ArrayList<>(); + + List<List<String>> batch = Lists.partition(ids, Integer.parseInt(STORAGE_RECORDS_BATCH_SIZE)); + for (List<String> recordsBatch : batch) { + Records storageOut = this.getRecords(recordsBatch); + valid.addAll(storageOut.getRecords()); + notFound.addAll(storageOut.getNotFound()); + conversionStatuses.addAll(storageOut.getConversionStatuses()); + missingRetryRecordIds.addAll(storageOut.getMissingRetryRecords()); + } + return Records.builder().records(valid).notFound(notFound).conversionStatuses(conversionStatuses).missingRetryRecords(missingRetryRecordIds).build(); + } + + private Records getRecords(List<String> ids) throws URISyntaxException { + // e.g. {"records":["test:10"]} + String body = this.gson.toJson(RecordIds.builder().records(ids).build()); + +// Map<String, String> headers = this.requestInfo.getHeadersMap(); + DpsHeaders headers = this.requestInfo.getHeaders(); + headers.put(FRAME_OF_REFERENCE, SLB_FRAME_OF_REFERENCE_VALUE); + FetchServiceHttpRequest request = FetchServiceHttpRequest + .builder() + .httpMethod(HttpMethods.POST) + .url(STORAGE_QUERY_RECORD_FOR_CONVERSION_HOST) + .headers(headers) + .body(body).build(); + HttpResponse response = this.urlFetchService.sendRequest(request); + return this.validateStorageResponse(response, ids); + } + + private Records validateStorageResponse(HttpResponse response, List<String> ids) { + String bulkStorageData = response.getBody(); + + // retry entire payload -- storage service returned empty response + if (Strings.isNullOrEmpty(bulkStorageData)) { + throw new AppException(HttpStatus.SC_NOT_FOUND, "Invalid request", "Storage service returned empty response"); + } + + Type recordsListType = new TypeToken<Records>() { + }.getType(); + Records records = this.gson.fromJson(bulkStorageData, recordsListType); + + // no retry possible, update record status as failed -- storage service cannot locate records + if (!records.getNotFound().isEmpty()) { + this.jobStatus.addOrUpdateRecordStatus(records.getNotFound(), IndexingStatus.FAIL, RequestStatus.INVALID_RECORD, "Storage service records not found", String.format("Storage service records not found: %s", String.join(",", records.getNotFound()))); + } + + List<Records.Entity> validRecords = records.getRecords(); + if (validRecords.isEmpty()) { + // no need to retry, ack the CloudTask message -- nothing to process from RecordChangeMessage batch + if (response.isSuccessCode()) { + throw new AppException(RequestStatus.INVALID_RECORD, "Invalid request", "Successful Storage service response with no valid records"); + } + + // retry entire payload -- storage service returned empty valid records with non-success response-code + jaxRsDpsLog.warning(String.format("unable to proceed, valid storage record not found. | upstream response code: %s | record ids: %s", response.getResponseCode(), String.join(" | ", ids))); + throw new AppException(HttpStatus.SC_NOT_FOUND, "Invalid request", "Storage service error"); + } + + Map<String, List<String>> conversionStatus = getConversionErrors(records.getConversionStatuses()); + for (Records.Entity storageRecord : validRecords) { + String recordId = storageRecord.getId(); + if (conversionStatus.get(recordId) == null) { + continue; + } + for (String status : conversionStatus.get(recordId)) { + this.jobStatus.addOrUpdateRecordStatus(recordId, IndexingStatus.WARN, HttpStatus.SC_BAD_REQUEST, status, String.format("record-id: %s | %s", recordId, status)); + } + } + + // retry missing records -- storage did not return response for all RecordChangeMessage record-ids + if (records.getTotalRecordCount() != ids.size()) { + List<String> missingRecords = this.getMissingRecords(records, ids); + records.setMissingRetryRecords(missingRecords); + this.jobStatus.addOrUpdateRecordStatus(missingRecords, IndexingStatus.FAIL, HttpStatus.SC_NOT_FOUND, "Partial response received from Storage service - missing records", String.format("Partial response received from Storage service: %s", String.join(",", missingRecords))); + } + + return records; + } + + private List<String> getMissingRecords(Records records, List<String> ids) { + List<String> validRecordIds = records.getRecords().stream().map(Records.Entity::getId).collect(Collectors.toList()); + List<String> invalidRecordsIds = records.getNotFound(); + List<String> requestedIds = new ArrayList<>(ids); + requestedIds.removeAll(validRecordIds); + requestedIds.removeAll(invalidRecordsIds); + return requestedIds; + } + + private Map<String, List<String>> getConversionErrors(List<ConversionStatus> conversionStatuses) { + Map<String, List<String>> errorsByRecordId = new HashMap<>(); + for (ConversionStatus conversionStatus : conversionStatuses) { + if (Strings.isNullOrEmpty(conversionStatus.getStatus())) continue; + if (conversionStatus.getStatus().equalsIgnoreCase("ERROR")) { + List<String> statuses = errorsByRecordId.getOrDefault(conversionStatus.getId(), new LinkedList<>()); + statuses.addAll(conversionStatus.getErrors()); + errorsByRecordId.put(conversionStatus.getId(), statuses); + } + } + return errorsByRecordId; + } + + @Override + public RecordQueryResponse getRecordsByKind(RecordReindexRequest reindexRequest) throws URISyntaxException { + Map<String, String> queryParams = new HashMap<>(); + queryParams.put(RecordMetaAttribute.KIND.getValue(), reindexRequest.getKind()); + queryParams.put("limit", STORAGE_RECORDS_BATCH_SIZE); + if (!Strings.isNullOrEmpty(reindexRequest.getCursor())) { + queryParams.put("cursor", reindexRequest.getCursor()); + } + + if(requestInfo == null) + throw new AppException(HttpStatus.SC_NO_CONTENT, "Invalid header", "header can't be null"); + + FetchServiceHttpRequest request = FetchServiceHttpRequest.builder() + .httpMethod(HttpMethods.GET) + .headers(this.requestInfo.getHeadersMap()) + .url(Config.getStorageQueryRecordHostUrl()) + .queryParams(queryParams) + .build(); + + HttpResponse response = this.urlFetchService.sendRequest(request); + return this.gson.fromJson(response.getBody(), RecordQueryResponse.class); + } + + @Override + public String getStorageSchema(String kind) throws URISyntaxException, UnsupportedEncodingException { + String url = String.format("%s/%s", STORAGE_SCHEMA_HOST, URLEncoder.encode(kind, StandardCharsets.UTF_8.toString())); + FetchServiceHttpRequest request = FetchServiceHttpRequest.builder() + .httpMethod(HttpMethods.GET) + .headers(this.requestInfo.getHeadersMap()) + .url(url) + .build(); + HttpResponse response = this.urlFetchService.sendRequest(request); + return response.getResponseCode() != HttpStatus.SC_OK ? null : response.getBody(); + } } \ No newline at end of file diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/ElasticClientHandler.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/ElasticClientHandler.java index 7035abf3ecb9820e4f09b1c7f3ff89966437152a..1007967178a2d2c4859ebb8f3f50c88d29c80afc 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/ElasticClientHandler.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/ElasticClientHandler.java @@ -1,108 +1,108 @@ -// 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.indexer.util; - -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.HttpStatus; -import org.apache.http.message.BasicHeader; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.search.ClusterSettings; -import org.opengroup.osdu.core.common.model.indexer.IElasticSettingService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.Base64; - -@Component -public class ElasticClientHandler { - - // Elastic cluster Rest client settings - private static final int CLOUD_REST_CLIENT_PORT = 9243; - private static final int REST_CLIENT_CONNECT_TIMEOUT = 60000; - private static final int REST_CLIENT_SOCKET_TIMEOUT = 60000; - private static final int REST_CLIENT_RETRY_TIMEOUT = 60000; - - @Autowired - private IElasticSettingService elasticSettingService; - - public RestHighLevelClient createRestClient() { - return getCloudRestClient(elasticSettingService.getElasticClusterInformation()); - } - // TODO: Remove this temporary implementation when ECE CCS is utilized - public RestHighLevelClient createRestClient(final ClusterSettings clusterSettings) { - return getCloudRestClient(clusterSettings); - } - - private RestHighLevelClient getCloudRestClient(final ClusterSettings clusterSettings) { - - String cluster = null; - String host = null; - int port = CLOUD_REST_CLIENT_PORT; - String protocolScheme = "https"; - String tls = "true"; - - try { - cluster = clusterSettings.getHost(); - host = clusterSettings.getHost(); - port = clusterSettings.getPort(); - if(!clusterSettings.isHttps()){ - protocolScheme = "http"; - } - - if(!clusterSettings.isTls()){ - tls = "false"; - } - String basicEncoded = Base64.getEncoder().encodeToString(clusterSettings.getUserNameAndPassword().getBytes()); - String basicAuthenticationHeaderVal = String.format("Basic %s", basicEncoded); - - RestClientBuilder builder = createClientBuilder(host, basicAuthenticationHeaderVal, port, protocolScheme, tls); - - return new RestHighLevelClient(builder); - } catch (AppException e) { - throw e; - } catch (Exception e) { - throw new AppException( - HttpStatus.SC_INTERNAL_SERVER_ERROR, - "search client error", - "error creating search client", - String.format("Elastic client connection params, cluster: %s, host: %s, port: %s", cluster, host, port), - e); - } - } - - public RestClientBuilder createClientBuilder(String host, String basicAuthenticationHeaderVal, int port, String protocolScheme, String tls) { - RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, protocolScheme)); - builder.setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(REST_CLIENT_CONNECT_TIMEOUT) - .setSocketTimeout(REST_CLIENT_SOCKET_TIMEOUT)); - builder.setMaxRetryTimeoutMillis(REST_CLIENT_RETRY_TIMEOUT); - - Header[] defaultHeaders = new Header[]{ - new BasicHeader("client.transport.nodes_sampler_interval", "30s"), - new BasicHeader("client.transport.ping_timeout", "30s"), - new BasicHeader("client.transport.sniff", "false"), - new BasicHeader("request.headers.X-Found-Cluster", host), - new BasicHeader("cluster.name", host), - new BasicHeader("xpack.security.transport.ssl.enabled", tls), - new BasicHeader("Authorization", basicAuthenticationHeaderVal), - }; - - builder.setDefaultHeaders(defaultHeaders); - return builder; - } +// 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.indexer.util; + +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.message.BasicHeader; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.search.ClusterSettings; +import org.opengroup.osdu.core.common.model.indexer.IElasticSettingService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Base64; + +@Component +public class ElasticClientHandler { + + // Elastic cluster Rest client settings + private static final int CLOUD_REST_CLIENT_PORT = 9243; + private static final int REST_CLIENT_CONNECT_TIMEOUT = 60000; + private static final int REST_CLIENT_SOCKET_TIMEOUT = 60000; + private static final int REST_CLIENT_RETRY_TIMEOUT = 60000; + + @Autowired + private IElasticSettingService elasticSettingService; + + public RestHighLevelClient createRestClient() { + return getCloudRestClient(elasticSettingService.getElasticClusterInformation()); + } + // TODO: Remove this temporary implementation when ECE CCS is utilized + public RestHighLevelClient createRestClient(final ClusterSettings clusterSettings) { + return getCloudRestClient(clusterSettings); + } + + private RestHighLevelClient getCloudRestClient(final ClusterSettings clusterSettings) { + + String cluster = null; + String host = null; + int port = CLOUD_REST_CLIENT_PORT; + String protocolScheme = "https"; + String tls = "true"; + + try { + cluster = clusterSettings.getHost(); + host = clusterSettings.getHost(); + port = clusterSettings.getPort(); + if(!clusterSettings.isHttps()){ + protocolScheme = "http"; + } + + if(!clusterSettings.isTls()){ + tls = "false"; + } + String basicEncoded = Base64.getEncoder().encodeToString(clusterSettings.getUserNameAndPassword().getBytes()); + String basicAuthenticationHeaderVal = String.format("Basic %s", basicEncoded); + + RestClientBuilder builder = createClientBuilder(host, basicAuthenticationHeaderVal, port, protocolScheme, tls); + + return new RestHighLevelClient(builder); + } catch (AppException e) { + throw e; + } catch (Exception e) { + throw new AppException( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + "search client error", + "error creating search client", + String.format("Elastic client connection params, cluster: %s, host: %s, port: %s", cluster, host, port), + e); + } + } + + public RestClientBuilder createClientBuilder(String host, String basicAuthenticationHeaderVal, int port, String protocolScheme, String tls) { + RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, protocolScheme)); + builder.setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(REST_CLIENT_CONNECT_TIMEOUT) + .setSocketTimeout(REST_CLIENT_SOCKET_TIMEOUT)); + builder.setMaxRetryTimeoutMillis(REST_CLIENT_RETRY_TIMEOUT); + + Header[] defaultHeaders = new Header[]{ + new BasicHeader("client.transport.nodes_sampler_interval", "30s"), + new BasicHeader("client.transport.ping_timeout", "30s"), + new BasicHeader("client.transport.sniff", "false"), + new BasicHeader("request.headers.X-Found-Cluster", host), + new BasicHeader("cluster.name", host), + new BasicHeader("xpack.security.transport.ssl.enabled", tls), + new BasicHeader("Authorization", basicAuthenticationHeaderVal), + }; + + builder.setDefaultHeaders(defaultHeaders); + return builder; + } } \ No newline at end of file diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/IndexerQueueTaskBuilder.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/IndexerQueueTaskBuilder.java index 3621691c10e7fb0e62aa153d781302798eb4d7f2..845ec58d784c740017eaf6dfe68468ee00b284e9 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/IndexerQueueTaskBuilder.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/IndexerQueueTaskBuilder.java @@ -1,72 +1,86 @@ -// 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.indexer.util; - -import com.google.api.client.http.HttpMethods; -import com.google.gson.Gson; -import lombok.extern.java.Log; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.search.CloudTaskRequest; -import org.opengroup.osdu.core.common.model.http.HttpResponse; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.core.common.http.IUrlFetchService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; - -import java.net.URISyntaxException; -import javax.inject.Inject; - -import static org.opengroup.osdu.core.common.Constants.REINDEX_RELATIVE_URL; -import static org.opengroup.osdu.core.common.Constants.WORKER_RELATIVE_URL; - - @Log - @Component - @RequestScope - public class IndexerQueueTaskBuilder { - - @Inject - private IUrlFetchService urlFetchService; - @Inject - private JaxRsDpsLog jaxRsDpsLog; - - @Value("${INDEXER_QUEUE_HOST}") - private String INDEXER_QUEUE_HOST; - - public void createWorkerTask(String payload, DpsHeaders headers) { - createTask(WORKER_RELATIVE_URL, payload, headers); - } - - public void createReIndexTask(String payload,DpsHeaders headers) { - createTask(REINDEX_RELATIVE_URL, payload, headers); - } - - private void createTask(String url, String payload, DpsHeaders headers) { - - CloudTaskRequest cloudTaskRequest = CloudTaskRequest.builder().message(payload).url(url).build(); - - try { - HttpResponse response = this.urlFetchService.sendRequest( - HttpMethods.POST, - INDEXER_QUEUE_HOST, - headers, - null, - new Gson().toJson(cloudTaskRequest)); - this.jaxRsDpsLog.info(String.format("task enqueuing response: %s", response.getResponseCode())); - } catch (URISyntaxException e) { - this.jaxRsDpsLog.warning(String.format("error enqueuing task message: %s | url: %s | task payload: %s", e.getMessage(), url, payload)); - } - } +// 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.indexer.util; + +import com.google.api.client.http.HttpMethods; +import com.google.gson.Gson; +import lombok.extern.java.Log; +import org.opengroup.osdu.core.common.http.FetchServiceHttpRequest; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.search.CloudTaskRequest; +import org.opengroup.osdu.core.common.model.http.HttpResponse; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.http.IUrlFetchService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.net.URISyntaxException; +import java.util.Map; +import javax.inject.Inject; + +import static org.opengroup.osdu.core.common.Constants.REINDEX_RELATIVE_URL; +import static org.opengroup.osdu.core.common.Constants.WORKER_RELATIVE_URL; + + @Log + @Component + @RequestScope + public class IndexerQueueTaskBuilder { + + @Inject + private IUrlFetchService urlFetchService; + @Inject + private JaxRsDpsLog jaxRsDpsLog; + + @Value("${INDEXER_QUEUE_HOST}") + private String INDEXER_QUEUE_HOST; + + public void createWorkerTask(String payload, DpsHeaders headers) { + createTask(WORKER_RELATIVE_URL, payload, 0l, headers); + } + + public void createWorkerTask(String payload, Long countdownMillis, DpsHeaders headers) { + createTask(WORKER_RELATIVE_URL, payload, countdownMillis, headers); + } + + public void createReIndexTask(String payload, DpsHeaders headers) { + createTask(REINDEX_RELATIVE_URL, payload, 0l, headers); + } + + public void createReIndexTask(String payload, Long countdownMillis, DpsHeaders headers) { + createTask(REINDEX_RELATIVE_URL, payload, countdownMillis, headers); + } + + private void createTask(String url, String payload, Long countdownMillis, DpsHeaders headers) { + CloudTaskRequest cloudTaskRequest = CloudTaskRequest.builder() + .message(payload) + .url(url) + .initialDelayMillis(countdownMillis) + .build(); + + FetchServiceHttpRequest request = FetchServiceHttpRequest.builder() + .httpMethod(HttpMethods.POST) + .url(INDEXER_QUEUE_HOST) + .body(new Gson().toJson(cloudTaskRequest)) + .headers(headers) + .build(); + try { + HttpResponse response = this.urlFetchService.sendRequest(request); + this.jaxRsDpsLog.info(String.format("task enqueuing response: %s", response.getResponseCode())); + } catch (URISyntaxException e) { + this.jaxRsDpsLog.warning(String.format("error enqueuing task message: %s | url: %s | task payload: %s", e.getMessage(), url, payload)); + } + } } \ No newline at end of file diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/TypeMapper.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/TypeMapper.java index 11b2935c2066a0f932714003089bd4b2489fb723..2f7c9b7c0e9b3051d6a428fec359be7418f35f93 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/TypeMapper.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/TypeMapper.java @@ -21,6 +21,7 @@ import org.opengroup.osdu.core.common.model.indexer.Records; import org.opengroup.osdu.core.common.model.indexer.StorageType; import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; +import org.apache.commons.lang3.StringUtils; import java.util.HashMap; import java.util.Map; @@ -44,22 +45,32 @@ public class TypeMapper { metaAttributeIndexerType.put(RecordMetaAttribute.INDEX_STATUS.getValue(), getIndexStatusMapping()); storageToIndexerType.put(StorageType.LINK.getValue(), ElasticType.KEYWORD.getValue()); - storageToIndexerType.put(StorageType.LINK_ARRAY.getValue(), ElasticType.KEYWORD.getValue()); + storageToIndexerType.put(StorageType.LINK_ARRAY.getValue(), ElasticType.KEYWORD_ARRAY.getValue()); storageToIndexerType.put(StorageType.BOOLEAN.getValue(), ElasticType.BOOLEAN.getValue()); + storageToIndexerType.put(StorageType.BOOLEAN_ARRAY.getValue(), ElasticType.BOOLEAN_ARRAY.getValue()); storageToIndexerType.put(StorageType.STRING.getValue(), ElasticType.TEXT.getValue()); + storageToIndexerType.put(StorageType.STRING_ARRAY.getValue(), ElasticType.TEXT_ARRAY.getValue()); storageToIndexerType.put(StorageType.INT.getValue(), ElasticType.INTEGER.getValue()); + storageToIndexerType.put(StorageType.INT_ARRAY.getValue(), ElasticType.INTEGER_ARRAY.getValue()); storageToIndexerType.put(StorageType.FLOAT.getValue(), ElasticType.FLOAT.getValue()); + storageToIndexerType.put(StorageType.FLOAT_ARRAY.getValue(), ElasticType.FLOAT_ARRAY.getValue()); storageToIndexerType.put(StorageType.DOUBLE.getValue(), ElasticType.DOUBLE.getValue()); - storageToIndexerType.put(StorageType.DOUBLE_ARRAY.getValue(), ElasticType.DOUBLE.getValue()); + storageToIndexerType.put(StorageType.DOUBLE_ARRAY.getValue(), ElasticType.DOUBLE_ARRAY.getValue()); storageToIndexerType.put(StorageType.LONG.getValue(), ElasticType.LONG.getValue()); + storageToIndexerType.put(StorageType.LONG_ARRAY.getValue(), ElasticType.LONG_ARRAY.getValue()); storageToIndexerType.put(StorageType.DATETIME.getValue(), ElasticType.DATE.getValue()); + storageToIndexerType.put(StorageType.DATETIME_ARRAY.getValue(), ElasticType.DATE_ARRAY.getValue()); storageToIndexerType.put(StorageType.GEO_POINT.getValue(), ElasticType.GEO_POINT.getValue()); storageToIndexerType.put(StorageType.GEO_SHAPE.getValue(), ElasticType.GEO_SHAPE.getValue()); } public static String getIndexerType(String storageType) { - return storageToIndexerType.getOrDefault(storageType, null); + String indexedType = storageToIndexerType.getOrDefault(storageType, null); + if (indexedType != null && indexedType.endsWith("_array")) { + return StringUtils.substringBefore(indexedType, "_"); + } + return indexedType; } public static Object getIndexerType(RecordMetaAttribute attribute) { diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/parser/BooleanParser.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/parser/BooleanParser.java new file mode 100644 index 0000000000000000000000000000000000000000..3bb08d3fc3274f2fab8a22114567ac0637ef3653 --- /dev/null +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/parser/BooleanParser.java @@ -0,0 +1,15 @@ +package org.opengroup.osdu.indexer.util.parser; + +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +public class BooleanParser { + + public boolean parseBoolean(String attributeName, Object attributeVal) { + String val = attributeVal == null ? null : String.valueOf(attributeVal); + return Boolean.parseBoolean(val); + } + +} diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/parser/DateTimeParser.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/parser/DateTimeParser.java index 28ba4a47b0748b5432ba046e33ac3e3320108bfe..1fcf39368710b04d93768a05a560b5a21f95798e 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/parser/DateTimeParser.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/parser/DateTimeParser.java @@ -83,4 +83,17 @@ public class DateTimeParser { } return null; } + + public String parseDate(String attributeName, Object attributeVal) { + String val = attributeVal == null ? null : String.valueOf(attributeVal); + if (Strings.isNullOrEmpty(val)) { + // skip indexing + return null; + } + String utcDate = this.convertDateObjectToUtc(val); + if (Strings.isNullOrEmpty(utcDate)) { + throw new IllegalArgumentException(String.format("datetime parsing error: unknown format for attribute: %s | value: %s", attributeName, attributeVal)); + } + return utcDate; + } } \ No newline at end of file diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/ReindexApiTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/ReindexApiTest.java index 88a101b5df8f2e996b0602ac47c7940292ed01d3..089f8417a3a853b966f5f0524e7ee07e364db955 100644 --- a/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/ReindexApiTest.java +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/api/ReindexApiTest.java @@ -1,74 +1,77 @@ -// 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.indexer.api; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.indexer.logging.AuditLogger; -import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; -import org.opengroup.osdu.indexer.service.ReindexService; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.junit4.SpringRunner; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.when; - -@RunWith(SpringRunner.class) -public class ReindexApiTest { - - private RecordReindexRequest recordReindexRequest; - - @Mock - private ReindexService reIndexService; - @Mock - private AuditLogger auditLogger; - @InjectMocks - private ReindexApi sut; - - @Before - public void setup() { - recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor("100").build(); - } - - @Test - public void should_return200_when_valid_kind_provided() { - when(this.reIndexService.reindexRecords(recordReindexRequest)).thenReturn("something"); - - ResponseEntity response = sut.reindex(recordReindexRequest); - - assertEquals(HttpStatus.OK.value(), response.getStatusCodeValue()); - } - - @Test(expected = AppException.class) - public void should_throwAppException_ifUnknownExceptionCaught_reindexTest() { - when(this.reIndexService.reindexRecords(any())).thenThrow(new AppException(500, "", "")); - - sut.reindex(recordReindexRequest); - } - - @Test(expected = NullPointerException.class) - public void should_throwAppException_ifNullPointerExceptionCaught_ReindexTest() { - when(this.reIndexService.reindexRecords(any())).thenThrow(new NullPointerException("")); - - sut.reindex(recordReindexRequest); - } -} +// 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.indexer.api; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; +import org.opengroup.osdu.indexer.logging.AuditLogger; +import org.opengroup.osdu.indexer.service.IndexSchemaService; +import org.opengroup.osdu.indexer.service.ReindexService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) +public class ReindexApiTest { + + private RecordReindexRequest recordReindexRequest; + + @Mock + private ReindexService reIndexService; + @Mock + private IndexSchemaService indexSchemaService; + @Mock + private AuditLogger auditLogger; + @InjectMocks + private ReindexApi sut; + + @Before + public void setup() { + recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor("100").build(); + } + + @Test + public void should_return200_when_valid_kind_provided() throws IOException { + when(this.reIndexService.reindexRecords(recordReindexRequest, false)).thenReturn("something"); + + ResponseEntity<?> response = sut.reindex(recordReindexRequest, false); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + @Test(expected = AppException.class) + public void should_throwAppException_ifUnknownExceptionCaught_reindexTest() throws IOException { + when(this.reIndexService.reindexRecords(recordReindexRequest, false)).thenThrow(new AppException(500, "", "")); + + sut.reindex(recordReindexRequest, false); + } + + @Test(expected = NullPointerException.class) + public void should_throwAppException_ifNullPointerExceptionCaught_ReindexTest() throws IOException { + when(this.reIndexService.reindexRecords(recordReindexRequest, false)).thenThrow(new NullPointerException("")); + + sut.reindex(recordReindexRequest, false); + } +} diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImplTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImplTest.java index 73da01c76e4f01de80d4b24ddae63cacb453a584..5db76eb05d6eaffa535320fe4603b83191e40d26 100644 --- a/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImplTest.java +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/service/AttributeParsingServiceImplTest.java @@ -21,8 +21,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; import org.opengroup.osdu.core.common.model.indexer.JobStatus; +import org.opengroup.osdu.indexer.util.parser.BooleanParser; import org.opengroup.osdu.indexer.util.parser.DateTimeParser; import org.opengroup.osdu.indexer.util.parser.GeoShapeParser; import org.opengroup.osdu.indexer.util.parser.NumberParser; @@ -31,6 +33,7 @@ import org.springframework.test.context.junit4.SpringRunner; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; +import java.util.Date; import static org.junit.Assert.*; import static org.mockito.Matchers.any; @@ -41,10 +44,12 @@ public class AttributeParsingServiceImplTest { @Mock private GeometryConversionService geometryConversionService; - @Mock - private NumberParser numberParser; - @Mock - private DateTimeParser dateTimeParser; + @Spy + private BooleanParser booleanParser = new BooleanParser(); + @Spy + private NumberParser numberParser = new NumberParser(); + @Spy + private DateTimeParser dateTimeParser = new DateTimeParser(); @Mock private GeoShapeParser geoShapeParser; @Mock @@ -58,17 +63,45 @@ public class AttributeParsingServiceImplTest { public void should_parseValidInteger() { Map<String, Object> dataMap = new HashMap<>(); - when(this.numberParser.parseInteger(any(), any())).thenThrow(new IllegalArgumentException("number parsing error, integer out of range: attribute: lat | value: 101959.E1019594E")); - this.sut.tryParseInteger("common:welldb:wellbore-OGY4ZWQ5", "lat", "101959.E1019594E", dataMap); - assertEquals(dataMap.size(), 0); - assertFalse(dataMap.containsKey("lat")); + this.sut.tryParseInteger("common:welldb:wellbore-OGY4ZWQ5", "lat", "101959.15", dataMap); + assertEquals(dataMap.size(), 1); + assertTrue(dataMap.containsKey("lat")); + } + + @Test + public void should_parseValidIntegerArray() { + Map<String, Object> dataMap = new HashMap<>(); + final Map<Object, Object> inputs = new HashMap<Object, Object>() { + { + put("[]", new Integer[]{}); + put("[101959.1]", new Integer[]{101959}); + put("[139, 20]", new Integer[]{139, 20}); + put("[\"139.987\", \"20\"]", new Integer[]{139, 20}); + } + }; + + this.validateInput(this.sut::tryParseValueArray, Integer.class, Integer.class, inputs, dataMap); + } + + @Test + public void should_parseValidLongArray() { + Map<String, Object> dataMap = new HashMap<>(); + final Map<Object, Object> inputs = new HashMap<Object, Object>() { + { + put("[]", new Long[]{}); + put("[4115420654264075766]", new Long[]{4115420654264075766L}); + put("[4115420, 20]", new Long[]{4115420L, 20L}); + put("[\"139\", \"20\"]", new Long[]{139L, 20L}); + } + }; + + this.validateInput(this.sut::tryParseValueArray, Long.class, Long.class, inputs, dataMap); } @Test public void should_parseValidLong() { Map<String, Object> dataMap = new HashMap<>(); - when(this.numberParser.parseLong(any(), any())).thenReturn(0L); this.sut.tryParseLong("common:welldb:wellbore-OGY4ZWQ5", "reference", "", dataMap); assertEquals(dataMap.size(), 1); assertEquals(dataMap.get("reference"), 0L); @@ -78,22 +111,50 @@ public class AttributeParsingServiceImplTest { public void should_parseValidFloat() { Map<String, Object> dataMap = new HashMap<>(); - when(this.numberParser.parseFloat(any(), any())).thenReturn(0f); this.sut.tryParseFloat("common:welldb:wellbore-MjVhND", "lon", null, dataMap); assertEquals(dataMap.size(), 1); assertEquals(dataMap.get("lon"), 0.0f); } + @Test + public void should_parseValidFloatArray() { + Map<String, Object> dataMap = new HashMap<>(); + final Map<Object, Object> inputs = new HashMap<Object, Object>() { + { + put("[]", new Float[]{}); + put("[101959.1]", new Float[]{101959.1f}); + put("[139.90, 20.7]", new Float[]{139.90f, 20.7f}); + put("[\"139.987\", \"20\"]", new Float[]{139.987f, 20.0f}); + } + }; + + this.validateInput(this.sut::tryParseValueArray, Float.class, Float.class, inputs, dataMap); + } + @Test public void should_parseValidDouble() { Map<String, Object> dataMap = new HashMap<>(); - when(this.numberParser.parseDouble(any(), any())).thenReturn(20.0); this.sut.tryParseDouble("common:welldb:wellbore-zMWQtMm", "location", 20.0, dataMap); assertEquals(dataMap.size(), 1); assertEquals(dataMap.get("location"), 20.0); } + @Test + public void should_parseValidDoubleArray() { + Map<String, Object> dataMap = new HashMap<>(); + final Map<Object, Object> inputs = new HashMap<Object, Object>() { + { + put("[]", new Double[]{}); + put("[101959.1]", new Double[]{101959.1}); + put("[139.1, 20.0]", new Double[]{139.1, 20.0}); + put("[\"139.9\", \"20.1\"]", new Double[]{139.9, 20.1}); + } + }; + + this.validateInput(this.sut::tryParseValueArray, Double.class, Double.class, inputs, dataMap); + } + @Test public void should_parseBoolean() { Map<String, Object> dataMap = new HashMap<>(); @@ -119,6 +180,21 @@ public class AttributeParsingServiceImplTest { assertEquals(dataMap.get("side"), true); } + @Test + public void should_parseValidBooleanArray() { + Map<String, Object> dataMap = new HashMap<>(); + final Map<Object, Object> inputs = new HashMap<Object, Object>() { + { + put("[]", new Boolean[]{}); + put("[true]", new Boolean[]{true}); + put("[false, truee]", new Boolean[]{false, false}); + put("[\"true\", \"false\"]", new Boolean[]{true, false}); + } + }; + + this.validateInput(this.sut::tryParseValueArray, Boolean.class, Boolean.class, inputs, dataMap); + } + @Test public void should_parseDate_tryParseDate() { Map<String, Object> dataMap = new HashMap<>(); @@ -135,13 +211,28 @@ public class AttributeParsingServiceImplTest { assertEquals(dataMap.size(), 0); assertFalse(dataMap.containsKey("activatedOn")); - when(this.dateTimeParser.convertDateObjectToUtc("2018-11-06T19:37:11.128Z")).thenReturn("2018-11-06T19:37:11+0000"); + when(this.dateTimeParser.parseDate("disabledOn", "2018-11-06T19:37:11.128Z")).thenReturn("2018-11-06T19:37:11+0000"); this.sut.tryParseDate("common:welldb:wellbore-OGY4ZWQ5", "disabledOn", "2018-11-06T19:37:11.128Z", dataMap); assertEquals(dataMap.size(), 1); assertTrue(dataMap.containsKey("disabledOn")); assertEquals(dataMap.get("disabledOn"), "2018-11-06T19:37:11+0000"); } + @Test + public void should_parseValidDateArray() { + Map<String, Object> dataMap = new HashMap<>(); + final Map<Object, Object> inputs = new HashMap<Object, Object>() { + { + put("[]", new String[]{}); + put("[\"2018-11-06T19:37:11.128Z\"]", new String[]{"2018-11-06T19:37:11.128+0000"}); + put("[20000102, 2000-01-02]", new String[]{"2000-01-02T00:00:00+0000", "2000-01-02T00:00:00+0000"}); + // TODO: put("[2018-11-06T19:37:11.128Z]", new String[]{"2018-11-06T19:37:11.128+0000"}); + } + }; + + this.validateInput(this.sut::tryParseValueArray, Date.class, String.class, inputs, dataMap); + } + @Test public void should_notReturnLatLong_given_oneOfTheNullAttribute_tryGetGeopointTest() { LinkedTreeMap<String, Object> positionTreeMap = new LinkedTreeMap<>(); @@ -258,4 +349,32 @@ public class AttributeParsingServiceImplTest { Type type = new TypeToken<Map<String, Object>>() {}.getType(); return new Gson().fromJson(json, type); } + + private <I, O> void validateInput(QuintConsumer<Class<I>, String, String, Object, Map<String, Object>> parser, Class<I> inputType, Class<O> expectedType, Map<Object, Object> inputs, Map<String, Object> outMap) { + inputs.forEach((attributeVal, expectedOut) -> { + try { + parser.accept(inputType, "dummyId", "dummyAttribute", attributeVal, outMap); + + assertEquals(outMap.size(), 1); + assertTrue(outMap.containsKey("dummyAttribute")); + assertArrayEquals((I[]) outMap.get("dummyAttribute"), (O[]) expectedOut); + } catch (IllegalArgumentException e) { + fail(String.format("Parsing exception expected for %s with value [ %s ]", inputType.getName(), attributeVal)); + } + }); + } + + @FunctionalInterface + private interface QuintConsumer<T, U, V, W, X> { + /** + * Applies this function to the given arguments. + * + * @param t the first function argument + * @param u the second function argument + * @param v the third function argument + * @param w the fourth function argument + * * @return the function result + */ + void accept(T t, U u, V v, W w, X x); + } } \ No newline at end of file diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/JobStatusTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/JobStatusTest.java index 9e61f1cde0e3b673086cd13a671db65a9bb18d3a..79a0f7f0d504791f7f0c99a18a082d26e50b1b23 100644 --- a/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/JobStatusTest.java +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/JobStatusTest.java @@ -33,7 +33,6 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; import static org.junit.Assert.*; @@ -196,7 +195,7 @@ public class JobStatusTest { jobStatus.addOrUpdateRecordStatus("skipped2", IndexingStatus.SKIP, 1, ""); jobStatus.addOrUpdateRecordStatus("skipped3", IndexingStatus.SKIP, 1, ""); jobStatus.addOrUpdateRecordStatus("skipped4", IndexingStatus.SKIP, 1, ""); - jobStatus.getIdsByIndexingStatus(IndexingStatus.SUCCESS); + jobStatus.addOrUpdateRecordStatus("warn1", IndexingStatus.WARN, 1, ""); return jobStatus; } @@ -217,6 +216,13 @@ public class JobStatusTest { assertEquals(statuses.size(), 0); } + @Test + public void should_get_correctUpdateMessageCount_given_updateRecordStatusTest() { + JobStatus jobStatus = insertTestCasesIntoJobStatus(); + + assertEquals(6, jobStatus.getIdsByValidUpsertIndexingStatus().size()); + } + @Test public void should_returnValidList_getRecordStatuses() { @@ -233,7 +239,10 @@ public class JobStatusTest { assertNotNull(statuses); assertEquals(1, statuses.size()); - List<String> toString = statuses.stream().map(RecordStatus::toString).collect(Collectors.toList()); - assertEquals(toString.get(0), "RecordStatus(id=tenant1:doc:test2, kind=tenant1:testindexer12:well:1.0.0, operationType=create, status=SUCCESS)"); + RecordStatus recordStatus = statuses.get(0); + assertEquals("tenant1:doc:test2", recordStatus.getId()); + assertEquals("tenant1:testindexer12:well:1.0.0", recordStatus.getKind()); + assertEquals("create", recordStatus.getOperationType()); + assertEquals(IndexingStatus.SUCCESS, recordStatus.getStatus()); } } diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/parser/BooleanParserTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/parser/BooleanParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b12673e1944a71d36fe586444f061384a36b34a6 --- /dev/null +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/parser/BooleanParserTest.java @@ -0,0 +1,27 @@ +package org.opengroup.osdu.indexer.util.parser; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(MockitoJUnitRunner.class) +public class BooleanParserTest { + + @InjectMocks + private BooleanParser sut; + + @Test + public void should_parseBoolean() { + assertTrue(this.sut.parseBoolean("testBooleanAttribute", "true")); + assertTrue(this.sut.parseBoolean("testBooleanAttribute", "TRUE")); + + assertFalse(this.sut.parseBoolean("testBooleanAttribute", "")); + assertFalse(this.sut.parseBoolean("testBooleanAttribute", null)); + assertFalse(this.sut.parseBoolean("testBooleanAttribute", "false")); + assertFalse(this.sut.parseBoolean("testBooleanAttribute", "truee")); + } +} \ No newline at end of file diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/parser/DateTimeParserTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/parser/DateTimeParserTest.java index a4fd49f65eee0a76c22926ae5017284f9b10b7e7..9aa1196043357b199ed24a9b723fd6b398b6aa35 100644 --- a/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/parser/DateTimeParserTest.java +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/parser/DateTimeParserTest.java @@ -89,4 +89,15 @@ public class DateTimeParserTest { assertNull(this.sut.convertDateObjectToUtc(".2190851121908511EE44")); assertNull(this.sut.convertDateObjectToUtc("E.2131")); } + + @Test(expected = IllegalArgumentException.class) + public void should_throwException_given_invalidDate_parseDateTest() { + this.sut.parseDate("testDateTimeAttribute", "N/A"); + } + + @Test + public void should_returnNull_given_emptyOrNull_parseDateTest() { + assertNull(this.sut.parseDate("testDateTimeAttribute", "")); + assertNull(this.sut.parseDate("testDateTimeAttribute", null)); + } } diff --git a/pom.xml b/pom.xml index 0ce2c8068920277504c25f3b6557316db50eeec2..15379935ce07282cb0f38e82c8463072332df8ae 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ <java.version>1.8</java.version> <springfox-version>2.7.0</springfox-version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> + <os-core-common.version>0.0.18</os-core-common.version> <!-- <maven.compiler.target>1.8</maven.compiler.target>--> <!-- <maven.compiler.source>1.8</maven.compiler.source>--> <!-- <maven.war.plugin>2.6</maven.war.plugin>--> @@ -57,6 +58,16 @@ </snapshotRepository> </distributionManagement> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.opengroup.osdu</groupId> + <artifactId>os-core-common</artifactId> + <version>${os-core-common.version}</version> + </dependency> + </dependencies> + </dependencyManagement> + <modules> <module>indexer-core</module> <module>provider/indexer-aws</module> diff --git a/provider/indexer-azure/pom.xml b/provider/indexer-azure/pom.xml index 3ad7260a2e4f24e716f197532388004322c1ea53..137dc152137760e152bb1db9938cd044bc7c5995 100644 --- a/provider/indexer-azure/pom.xml +++ b/provider/indexer-azure/pom.xml @@ -88,7 +88,6 @@ <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>os-core-common</artifactId> - <version>0.0.13</version> </dependency> <dependency> <groupId>org.opengroup.osdu.indexer</groupId> diff --git a/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/IndexerSchemaServiceTest.java b/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/IndexerSchemaServiceTest.java index 9961c312f01e4ab7695546afc5f881fd3e15624b..bc640485be50de6f1727e4f9bc16306ec01ea1b0 100644 --- a/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/IndexerSchemaServiceTest.java +++ b/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/IndexerSchemaServiceTest.java @@ -87,7 +87,7 @@ public class IndexerSchemaServiceTest { public void should_returnNull_givenEmptySchema_getIndexerInputSchemaSchemaTest() throws Exception { when(storageService.getStorageSchema(any())).thenReturn(emptySchema); - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); Assert.assertNotNull(indexSchema); } @@ -96,7 +96,7 @@ public class IndexerSchemaServiceTest { public void should_returnValidResponse_givenValidSchema_getIndexerInputSchemaTest() throws Exception { when(storageService.getStorageSchema(any())).thenReturn(someSchema); - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); Assert.assertEquals(kind, indexSchema.getKind()); } @@ -106,7 +106,7 @@ public class IndexerSchemaServiceTest { when(storageService.getStorageSchema(any())).thenReturn(someSchema); when(this.schemaCache.get(kind + "_flattened")).thenReturn(someSchema); - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); Assert.assertEquals(kind, indexSchema.getKind()); } @@ -117,7 +117,7 @@ public class IndexerSchemaServiceTest { String invalidSchema = "{}}"; when(storageService.getStorageSchema(any())).thenReturn(invalidSchema); - this.sut.getIndexerInputSchema(kind); + this.sut.getIndexerInputSchema(kind, false); fail("Should throw exception"); } catch (AppException e) { Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getError().getCode()); @@ -129,7 +129,7 @@ public class IndexerSchemaServiceTest { @Test public void should_return_basic_schema_when_storage_returns_no_schema() { - IndexSchema returnedSchema = this.sut.getIndexerInputSchema(kind); + IndexSchema returnedSchema = this.sut.getIndexerInputSchema(kind, false); assertNotNull(returnedSchema.getDataSchema()); assertNotNull(returnedSchema); @@ -326,4 +326,82 @@ public class IndexerSchemaServiceTest { verify(this.log).warning(eq(String.format("Kind: %s not found", kind))); } + + @Test + public void should_sync_schema_with_storage() throws Exception { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.indicesService.deleteIndex(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.syncIndexMappingWithStorageSchema(kind); + + verify(this.mappingService, times(1)).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, times(1)).isIndexExist(any(), any()); + verify(this.indicesService, times(1)).deleteIndex(any(), any()); + verify(this.indicesService, times(1)).createIndex(any(), any(), any(), any(), any()); + verifyNoMoreInteractions(this.mappingService); + } + + @Test + public void should_throw_exception_while_snapshot_running_sync_schema_with_storage() throws Exception { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.indicesService.deleteIndex(any(), any())).thenThrow(new AppException(HttpStatus.SC_CONFLICT, "Index deletion error", "blah")); + + try { + this.sut.syncIndexMappingWithStorageSchema(kind); + } catch (AppException e) { + assertEquals(e.getError().getCode(), HttpStatus.SC_CONFLICT); + assertEquals(e.getError().getMessage(), "blah"); + assertEquals(e.getError().getReason(), "Index deletion error"); + } catch (Exception e) { + fail("Should not throw this exception " + e.getMessage()); + } + + verify(this.indicesService, times(1)).isIndexExist(any(), any()); + verify(this.indicesService, times(1)).deleteIndex(any(), any()); + verify(this.mappingService, never()).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, never()).createIndex(any(), any(), any(), any(), any()); + } + + @Test + public void should_return_true_while_if_forceClean_requested() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + + assertTrue(this.sut.isStorageSchemaSyncRequired(kind, true)); + } + + @Test + public void should_return_true_while_if_forceClean_notRequested_and_indexNotFound() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); + + assertTrue(this.sut.isStorageSchemaSyncRequired(kind, false)); + } + + @Test + public void should_return_false_while_if_forceClean_notRequested_and_indexExist() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + + assertFalse(this.sut.isStorageSchemaSyncRequired(kind, false)); + } } diff --git a/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/ReindexServiceTest.java b/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/ReindexServiceTest.java index 711a3e19ed996ffccb30631702f618f53da4210e..5120f069f072945e85a569cb3d5b49a74251fab8 100644 --- a/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/ReindexServiceTest.java +++ b/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/ReindexServiceTest.java @@ -27,10 +27,14 @@ import org.opengroup.osdu.core.common.model.http.DpsHeaders; import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.search.Config; import org.opengroup.osdu.indexer.service.ReindexServiceImpl; import org.opengroup.osdu.indexer.service.StorageService; import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; import org.springframework.test.context.junit4.SpringRunner; import java.util.*; @@ -41,7 +45,9 @@ import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; @Ignore -@RunWith(SpringRunner.class) +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(SpringRunner.class) +@PrepareForTest({Config.class}) public class ReindexServiceTest { private final String cursor = "100"; @@ -88,7 +94,7 @@ public class ReindexServiceTest { recordQueryResponse.setResults(null); when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - String response = sut.reindexRecords(recordReindexRequest); + String response = sut.reindexRecords(recordReindexRequest, false); Assert.assertNull(response); } catch (Exception e) { @@ -102,7 +108,7 @@ public class ReindexServiceTest { recordQueryResponse.setResults(new ArrayList<>()); when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - String response = sut.reindexRecords(recordReindexRequest); + String response = sut.reindexRecords(recordReindexRequest, false); Assert.assertNull(response); } catch (Exception e) { @@ -117,9 +123,13 @@ public class ReindexServiceTest { List<String> results = new ArrayList<>(); results.add("test1"); recordQueryResponse.setResults(results); + + mockStatic(Config.class); + when(Config.getStorageRecordsBatchSize()).thenReturn(1); + when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - String taskQueuePayload = sut.reindexRecords(recordReindexRequest); + String taskQueuePayload = sut.reindexRecords(recordReindexRequest, false); Assert.assertEquals("{\"kind\":\"tenant:test:test:1.0.0\",\"cursor\":\"100\"}", taskQueuePayload); } catch (Exception e) { @@ -135,7 +145,7 @@ public class ReindexServiceTest { recordQueryResponse.setResults(results); when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - String taskQueuePayload = sut.reindexRecords(recordReindexRequest); + String taskQueuePayload = sut.reindexRecords(recordReindexRequest, false); Assert.assertEquals(String.format("{\"data\":\"[{\\\"id\\\":\\\"test1\\\",\\\"kind\\\":\\\"tenant:test:test:1.0.0\\\",\\\"op\\\":\\\"create\\\"}]\",\"attributes\":{\"slb-correlation-id\":\"%s\"}}", correlationId), taskQueuePayload); } catch (Exception e) { diff --git a/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/StorageServiceTest.java b/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/StorageServiceTest.java index aedf8960acca1ecf4d3807234d6c048dcaa8ff55..d32955169190c779486a55fe6b549b7ef7b20c09 100644 --- a/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/StorageServiceTest.java +++ b/provider/indexer-azure/src/test/java/org/opengroup/osdu/indexer/azure/service/StorageServiceTest.java @@ -24,6 +24,7 @@ import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; +import org.opengroup.osdu.core.common.model.indexer.IndexingStatus; import org.opengroup.osdu.core.common.model.indexer.RecordInfo; import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; @@ -44,9 +45,11 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import static java.util.Collections.singletonList; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.when; @Ignore @@ -65,12 +68,14 @@ public class StorageServiceTest { private StorageServiceImpl sut; private List<String> ids; + private static final String RECORD_ID1 = "tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465"; + private static final String RECORDS_ID2 = "tenant1:doc:15e790a69beb4d789b1f979e2af2e813"; @Before public void setup() { String recordChangedMessages = "[{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"purge\"}," + - "{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"create\"}]"; + "{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"create\"}]"; when(this.requestInfo.getHeadersMap()).thenReturn(new HashMap<>()); @@ -78,7 +83,7 @@ public class StorageServiceTest { List<RecordInfo> msgs = (new Gson()).fromJson(recordChangedMessages, listType); jobStatus.initialize(msgs); - ids = Arrays.asList("tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465", "tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465"); + ids = Arrays.asList(RECORD_ID1, RECORDS_ID2); } @Test @@ -87,7 +92,7 @@ public class StorageServiceTest { HttpResponse httpResponse = mock(HttpResponse.class); Mockito.when(httpResponse.getBody()).thenReturn(null); - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); should_return404_getValidStorageRecordsTest(); } @@ -100,7 +105,7 @@ public class StorageServiceTest { HttpResponse httpResponse = mock(HttpResponse.class); Mockito.when(httpResponse.getBody()).thenReturn(emptyDataFromStorage); - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); should_return404_getValidStorageRecordsTest(); } @@ -113,12 +118,41 @@ public class StorageServiceTest { HttpResponse httpResponse = mock(HttpResponse.class); Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); Records storageRecords = this.sut.getStorageRecords(ids); assertEquals(1, storageRecords.getRecords().size()); } + @Test + public void should_logMissingRecord_given_storageMissedRecords() throws URISyntaxException { + + String validDataFromStorage = "{\"records\":[{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(ids); + + assertEquals(1, storageRecords.getRecords().size()); + verify(this.jobStatus).addOrUpdateRecordStatus(singletonList(RECORDS_ID2), IndexingStatus.FAIL, org.apache.http.HttpStatus.SC_NOT_FOUND, "Partial response received from Storage service - missing records", "Partial response received from Storage service: tenant1:doc:15e790a69beb4d789b1f979e2af2e813"); + } + + @Test + public void should_returnValidJobStatus_givenFailedUnitsConversion_processRecordChangedMessageTest() throws URISyntaxException { + String validDataFromStorage = "{\"records\":[{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[],\"conversionStatuses\":[{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\",\"status\":\"ERROR\",\"errors\":[\"crs conversion failed\"]}]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(singletonList("tenant1:doc:15e790a69beb4d789b1f979e2af2e813")); + + assertEquals(1, storageRecords.getRecords().size()); + verify(this.jobStatus).addOrUpdateRecordStatus("tenant1:doc:15e790a69beb4d789b1f979e2af2e813", IndexingStatus.WARN, org.apache.http.HttpStatus.SC_BAD_REQUEST, "crs conversion failed", String.format("record-id: %s | %s", "tenant1:doc:15e790a69beb4d789b1f979e2af2e813", "crs conversion failed")); + } + @Test public void should_returnValidResponse_givenValidRecordQueryRequest_getRecordListByKind() throws Exception { @@ -127,7 +161,7 @@ public class StorageServiceTest { HttpResponse httpResponse = new HttpResponse(); httpResponse.setBody(new Gson().toJson(recordReindexRequest, RecordReindexRequest.class)); - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); RecordQueryResponse recordQueryResponse = this.sut.getRecordsByKind(recordReindexRequest); @@ -158,7 +192,7 @@ public class StorageServiceTest { httpResponse.setResponseCode(HttpStatus.OK.value()); httpResponse.setBody(validSchemaFromStorage); - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); String recordSchemaResponse = this.sut.getStorageSchema(kind); @@ -173,7 +207,7 @@ public class StorageServiceTest { HttpResponse httpResponse = new HttpResponse(); httpResponse.setResponseCode(HttpStatus.NOT_FOUND.value()); - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); String recordSchemaResponse = this.sut.getStorageSchema(kind); @@ -183,19 +217,19 @@ public class StorageServiceTest { @Test public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsWithInvalidConversionTest() throws URISyntaxException { - String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"],\"conversionStatuses\": [{\"id\":\"testid\",\"status\":\"ERROR\",\"errors\":[\"conversion error occured\"] } ]}"; + String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"],\"conversionStatuses\": [{\"id\":\"testid\",\"status\":\"ERROR\",\"errors\":[\"conversion error occurred\"] } ]}"; HttpResponse httpResponse = mock(HttpResponse.class); Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); Records storageRecords = this.sut.getStorageRecords(ids); assertEquals(1, storageRecords.getRecords().size()); assertEquals(1, storageRecords.getConversionStatuses().get(0).getErrors().size()); - assertEquals("conversion error occured", storageRecords.getConversionStatuses().get(0).getErrors().get(0)); + assertEquals("conversion error occurred", storageRecords.getConversionStatuses().get(0).getErrors().get(0)); } private void should_return404_getValidStorageRecordsTest() { @@ -203,7 +237,7 @@ public class StorageServiceTest { this.sut.getStorageRecords(ids); fail("Should throw exception"); } catch (AppException e) { - assertEquals(HttpStatus.NOT_FOUND, e.getError().getCode()); + assertEquals(HttpStatus.NOT_FOUND.value(), e.getError().getCode()); } catch (Exception e) { fail("Should not throw this exception" + e.getMessage()); } diff --git a/provider/indexer-gcp/pom.xml b/provider/indexer-gcp/pom.xml index 795053370729a4d60b19488f45f28c4b24116f82..cadaf5037d5ff34b8aa2c1118e9684e456f88eee 100644 --- a/provider/indexer-gcp/pom.xml +++ b/provider/indexer-gcp/pom.xml @@ -49,7 +49,6 @@ <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>os-core-common</artifactId> - <version>0.0.13</version> </dependency> <dependency> @@ -137,7 +136,7 @@ <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>3.0.0</version> + <version>2.26.0</version> <scope>test</scope> </dependency> <dependency> diff --git a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/middleware/IndexFilterTest.java b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/middleware/IndexFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2b637c62629c28c5edfdce57f8821303fb6eb800 --- /dev/null +++ b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/middleware/IndexFilterTest.java @@ -0,0 +1,53 @@ +package org.opengroup.osdu.indexer.middleware; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.Collections; + +@RunWith(MockitoJUnitRunner.class) +public class IndexFilterTest { + + @InjectMocks + private IndexFilter indexFilter; + + @Mock + private DpsHeaders dpsHeaders; + + @Test + public void shouldSetCorrectResponseHeaders() throws IOException, ServletException { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + HttpServletResponse httpServletResponse = Mockito.mock(HttpServletResponse.class); + FilterChain filterChain = Mockito.mock(FilterChain.class); + Mockito.when(httpServletRequest.getRequestURI()).thenReturn("https://test.com"); + Mockito.when(httpServletRequest.getMethod()).thenReturn("POST"); + Mockito.when(dpsHeaders.getCorrelationId()).thenReturn("correlation-id-value"); + + indexFilter.doFilter(httpServletRequest, httpServletResponse, filterChain); + + Mockito.verify(httpServletResponse).addHeader("Access-Control-Allow-Origin", Collections.singletonList("*").toString()); + Mockito.verify(httpServletResponse).addHeader("Access-Control-Allow-Headers", Collections.singletonList("origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey").toString()); + Mockito.verify(httpServletResponse).addHeader("Access-Control-Allow-Methods", Collections.singletonList("GET, POST, PUT, DELETE, OPTIONS, HEAD").toString()); + Mockito.verify(httpServletResponse).addHeader("Access-Control-Allow-Credentials", Collections.singletonList("true").toString()); + Mockito.verify(httpServletResponse).addHeader("X-Frame-Options", Collections.singletonList("DENY").toString()); + Mockito.verify(httpServletResponse).addHeader("X-XSS-Protection", Collections.singletonList("1; mode=block").toString()); + Mockito.verify(httpServletResponse).addHeader("X-Content-Type-Options", Collections.singletonList("nosniff").toString()); + Mockito.verify(httpServletResponse).addHeader("Cache-Control", Collections.singletonList("no-cache, no-store, must-revalidate").toString()); + Mockito.verify(httpServletResponse).addHeader("Content-Security-Policy", Collections.singletonList("default-src 'self'").toString()); + Mockito.verify(httpServletResponse).addHeader("Strict-Transport-Security", Collections.singletonList("max-age=31536000; includeSubDomains").toString()); + Mockito.verify(httpServletResponse).addHeader("Expires", Collections.singletonList("0").toString()); + Mockito.verify(httpServletResponse).addHeader("correlation-id", "correlation-id-value"); + Mockito.verify(filterChain).doFilter(httpServletRequest, httpServletResponse); + } +} diff --git a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceTest.java b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceTest.java index 43f24cc6cdbe134714cdb2fd6ca4ce42c1ec8446..d28e1c2746ee215e10c1c460c799f7dda39e2fa8 100644 --- a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceTest.java +++ b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerMappingServiceTest.java @@ -1,314 +1,338 @@ -// 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.indexer.service; - -import org.apache.http.StatusLine; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData; -import org.elasticsearch.action.bulk.BulkItemResponse.Failure; -import org.elasticsearch.action.support.master.AcknowledgedResponse; -import org.elasticsearch.client.*; -import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.index.reindex.BulkByScrollResponse; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.indexer.IndexSchema; -import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; -import org.opengroup.osdu.indexer.util.ElasticClientHandler; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.io.IOException; -import java.util.*; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.powermock.api.mockito.PowerMockito.when; - -@Ignore -@RunWith(SpringRunner.class) -@PrepareForTest({ RestHighLevelClient.class, IndicesClient.class }) -public class IndexerMappingServiceTest { - - private final String kind = "tenant:test:test:1.0.0"; - private final String index = "tenant-test-test-1.0.0"; - private final String type = "test"; - private final String mappingValid = "{\"dynamic\":false,\"properties\":{\"data\":{\"properties\":{\"Location\":{\"type\":\"geo_point\"}}},\"id\":{\"type\":\"keyword\"}}}"; - - @Mock - private RestClient restClient; - @Mock - private Response response; - @Mock - private StatusLine statusLine; - - @InjectMocks - private IndexerMappingServiceImpl sut; - - @Mock - private ElasticClientHandler elasticClientHandler; - - @InjectMocks - private RestHighLevelClient restHighLevelClient; - - @InjectMocks - private IndexSchema indexSchema; - @InjectMocks - private IndicesClient indicesClient; - - @InjectMocks - private AcknowledgedResponse mappingResponse; - - @Before - public void setup() throws IOException { - Map<String, String> dataMapping = new HashMap<>(); - dataMapping.put("Location", "geo_point"); - Map<String, Object> metaMapping = new HashMap<>(); - metaMapping.put(RecordMetaAttribute.ID.getValue(), "keyword"); - this.indexSchema = IndexSchema.builder().kind(kind).type(type).dataSchema(dataMapping).metaSchema(metaMapping) - .build(); - - this.indicesClient = PowerMockito.mock(IndicesClient.class); - this.restHighLevelClient = PowerMockito.mock(RestHighLevelClient.class); - - when(this.restHighLevelClient.getLowLevelClient()).thenReturn(restClient); - when(this.restClient.performRequest(any())).thenReturn(response); - when(this.response.getStatusLine()).thenReturn(statusLine); - when(this.statusLine.getStatusCode()).thenReturn(200); - } - - @Test - public void should_returnValidMapping_givenFalseMerge_createMappingTest() { - try { - String mapping = this.sut.createMapping(restHighLevelClient, indexSchema, index, false); - assertEquals(mappingValid, mapping); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test - public void should_returnValidMapping_givenTrueMerge_createMappingTest() { - try { - doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); - doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); - - String mapping = this.sut.createMapping(this.restHighLevelClient, this.indexSchema, this.index, true); - assertEquals(this.mappingValid, mapping); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test - public void should_returnValidMapping_givenExistType_createMappingTest() { - try { - doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); - doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); - - IndexerMappingServiceImpl indexerMappingServiceLocal = PowerMockito.spy(new IndexerMappingServiceImpl()); - doReturn(false).when(indexerMappingServiceLocal).isTypeExist(any(), any(), any()); - String mapping = this.sut.createMapping(this.restHighLevelClient, this.indexSchema, this.index, true); - assertEquals(this.mappingValid, mapping); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test - public void should_update_indices_field_with_keyword_when_valid_indices() throws Exception { - try { - Set<String> indices = new HashSet<String>(); - indices.add("indices 1"); - GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); - doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); - when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); - XContentBuilder builder = XContentFactory.jsonBuilder(); - builder.startObject(); - builder.field("any field", new HashMap()); - builder.endObject(); - BytesReference bytesReference = BytesReference.bytes(builder); - FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); - Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); - mapBuilder.put("data.any field", mappingMetaData); - Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); - mappingBuilder.put("any index 1", mapBuilder); - mappingBuilder.put("any index 2", mapBuilder); - Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); - mapping.put("indices 1", mappingBuilder); - when(getFieldMappingsResponse.mappings()).thenReturn(mapping); - doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); - BulkByScrollResponse response = mock(BulkByScrollResponse.class); - doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); - when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); - when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); - - this.sut.updateIndexMappingForIndicesOfSameType( indices,"any field"); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test(expected = AppException.class) - public void should_throw_exception_if_someIndex_is_invalid_andWeIndexfield_with_keyword() throws Exception { - try { - Set<String> indices = new HashSet<String>(); - indices.add("invalid 1"); - GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); - doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); - when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); - XContentBuilder builder = XContentFactory.jsonBuilder(); - builder.startObject(); - builder.field("any field", new HashMap()); - builder.endObject(); - BytesReference bytesReference = BytesReference.bytes(builder); - FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); - Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); - mapBuilder.put("data.any field", mappingMetaData); - Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); - mappingBuilder.put("any index 1", mapBuilder); - mappingBuilder.put("any index 2", mapBuilder); - Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); - mapping.put("indices 1", mappingBuilder); - when(getFieldMappingsResponse.mappings()).thenReturn(mapping); - doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); - BulkByScrollResponse response = mock(BulkByScrollResponse.class); - doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); - when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); - when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); - - this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field"); - } catch (Exception e) { - throw e; - } - } - - @Test(expected = AppException.class) - public void should_throw_exception_if_type_of_index_is_invalid_andWeIndexfield_with_keyword() throws Exception { - try { - Set<String> indices = new HashSet<String>(); - indices.add("indices 1"); - GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); - doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); - when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); - XContentBuilder builder = XContentFactory.jsonBuilder(); - builder.startObject(); - builder.field("any field", new HashMap()); - builder.endObject(); - BytesReference bytesReference = BytesReference.bytes(builder); - FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); - Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); - mapBuilder.put("data.any field", mappingMetaData); - Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); - mappingBuilder.put("any index 1", mapBuilder); - mappingBuilder.put("any index 2", mapBuilder); - Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); - mapping.put("indices 1", mappingBuilder); - when(getFieldMappingsResponse.mappings()).thenReturn(mapping); - doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); - BulkByScrollResponse response = mock(BulkByScrollResponse.class); - doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); - when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); - when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); - this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field invalid"); - } catch (Exception e) { - throw e; - } - } - - @Test(expected = AppException.class) - public void should_throw_exception_if_elastic_search_failedToFetch_andWeIndexfield_with_keyword() throws Exception { - try { - - Set<String> indices = new HashSet<String>(); - indices.add("indices 1"); - indices.add("indices Invalid"); - GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); - doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); - when(this.indicesClient.getFieldMapping(any(), any())).thenThrow(new ElasticsearchException("")); - XContentBuilder builder = XContentFactory.jsonBuilder(); - builder.startObject(); - builder.field("any field", new HashMap()); - builder.endObject(); - BytesReference bytesReference = BytesReference.bytes(builder); - FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); - Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); - mapBuilder.put("data.any field", mappingMetaData); - Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); - mappingBuilder.put("any index 1", mapBuilder); - mappingBuilder.put("any index 2", mapBuilder); - Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); - mapping.put("indices 1", mappingBuilder); - when(getFieldMappingsResponse.mappings()).thenReturn(mapping); - doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); - BulkByScrollResponse response = mock(BulkByScrollResponse.class); - doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); - when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); - when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); - this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field"); - } catch (AppException e) { - throw e; - } - } - - @Test(expected = AppException.class) - public void should_throw_exception_when_elastic_failedToIndex_indices_field_with_keyword() { - try { - Set<String> indices = new HashSet<String>(); - indices.add("indices 1"); - indices.add("indices Invalid"); - GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); - doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); - when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); - XContentBuilder builder = XContentFactory.jsonBuilder(); - builder.startObject(); - builder.field("any field", new HashMap()); - builder.endObject(); - BytesReference bytesReference = BytesReference.bytes(builder); - FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); - Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); - mapBuilder.put("data.any field", mappingMetaData); - Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); - mappingBuilder.put("any index 1", mapBuilder); - mappingBuilder.put("any index 2", mapBuilder); - Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); - mapping.put("indices 1", mappingBuilder); - when(getFieldMappingsResponse.mappings()).thenReturn(mapping); - doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); - BulkByScrollResponse response = mock(BulkByScrollResponse.class); - doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); - when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); - when(this.indicesClient.putMapping(any(), any(RequestOptions.class))).thenThrow(new ElasticsearchException("")); - when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); - this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field"); - } catch (AppException e) { - throw e; - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } -} +// 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.indexer.service; + +import com.google.gson.Gson; +import org.apache.http.StatusLine; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; +import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData; +import org.elasticsearch.action.bulk.BulkItemResponse.Failure; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.client.*; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.reindex.BulkByScrollResponse; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.indexer.IndexSchema; +import org.opengroup.osdu.core.common.model.search.RecordMetaAttribute; +import org.opengroup.osdu.core.common.search.Config; +import org.opengroup.osdu.indexer.util.ElasticClientHandler; +import org.opengroup.osdu.indexer.util.TypeMapper; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.MockitoAnnotations.initMocks; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +@Ignore +@RunWith(SpringRunner.class) +@PrepareForTest({ RestHighLevelClient.class, IndicesClient.class, Config.class }) +public class IndexerMappingServiceTest { + + private final String kind = "tenant:test:test:1.0.0"; + private final String index = "tenant-test-test-1.0.0"; + private final String type = "test"; + private final String mappingValid = "{\"dynamic\":false,\"properties\":{\"data\":{\"properties\":{\"Msg\":{\"type\":\"text\",\"analyzer\":\"de_indexer_analyzer\",\"search_analyzer\":\"de_search_analyzer\"},\"Location\":{\"type\":\"geo_point\"}}},\"id\":{\"type\":\"keyword\"},\"acl\":{\"properties\":{\"viewers\":{\"type\":\"keyword\"},\"owners\":{\"type\":\"keyword\"}}}}}"; + + @Mock + private RestClient restClient; + @Mock + private Response response; + @Mock + private StatusLine statusLine; + + @InjectMocks + private IndexerMappingServiceImpl sut; + + @Mock + private ElasticClientHandler elasticClientHandler; + + @InjectMocks + private RestHighLevelClient restHighLevelClient; + + @InjectMocks + private IndexSchema indexSchema; + @InjectMocks + private IndicesClient indicesClient; + + @InjectMocks + private AcknowledgedResponse mappingResponse; + + @Before + public void setup() throws IOException { + initMocks(this); + mockStatic(Config.class); + when(Config.isPreDemo()).thenReturn(true); + Map<String, String> dataMapping = new HashMap<>(); + dataMapping.put("Location", "geo_point"); + dataMapping.put("Msg", "text"); + Map<String, Object> metaMapping = new HashMap<>(); + metaMapping.put(RecordMetaAttribute.ID.getValue(), "keyword"); + metaMapping.put(RecordMetaAttribute.ACL.getValue(), TypeMapper.getIndexerType(RecordMetaAttribute.ACL)); + this.indexSchema = IndexSchema.builder().kind(kind).type(type).dataSchema(dataMapping).metaSchema(metaMapping) + .build(); + + this.indicesClient = PowerMockito.mock(IndicesClient.class); + this.restHighLevelClient = PowerMockito.mock(RestHighLevelClient.class); + + when(this.restHighLevelClient.getLowLevelClient()).thenReturn(restClient); + when(this.restClient.performRequest(any())).thenReturn(response); + when(this.response.getStatusLine()).thenReturn(statusLine); + when(this.statusLine.getStatusCode()).thenReturn(200); + } + + @Test + public void should_returnValidMapping_givenFalseMerge_createMappingTest() { + try { + String mapping = this.sut.createMapping(restHighLevelClient, indexSchema, index, false); + assertEquals(mappingValid, mapping); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test + public void should_returnValidMapping_givenTrueMerge_createMappingTest() { + try { + doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); + doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); + + String mapping = this.sut.createMapping(this.restHighLevelClient, this.indexSchema, this.index, true); + assertEquals(this.mappingValid, mapping); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test + public void should_returnValidMapping_givenExistType_createMappingTest() { + try { + doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); + doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); + + IndexerMappingServiceImpl indexerMappingServiceLocal = PowerMockito.spy(new IndexerMappingServiceImpl()); + doReturn(false).when(indexerMappingServiceLocal).isTypeExist(any(), any(), any()); + String mapping = this.sut.createMapping(this.restHighLevelClient, this.indexSchema, this.index, true); + assertEquals(this.mappingValid, mapping); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test + public void should_update_indices_field_with_keyword_when_valid_indices() throws Exception { + try { + Set<String> indices = new HashSet<String>(); + indices.add("indices 1"); + GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); + doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); + when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("any field", new HashMap()); + builder.endObject(); + BytesReference bytesReference = BytesReference.bytes(builder); + FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); + Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); + mapBuilder.put("data.any field", mappingMetaData); + Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); + mappingBuilder.put("any index 1", mapBuilder); + mappingBuilder.put("any index 2", mapBuilder); + Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); + mapping.put("indices 1", mappingBuilder); + when(getFieldMappingsResponse.mappings()).thenReturn(mapping); + doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); + BulkByScrollResponse response = mock(BulkByScrollResponse.class); + doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); + when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + + this.sut.updateIndexMappingForIndicesOfSameType( indices,"any field"); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test(expected = AppException.class) + public void should_throw_exception_if_someIndex_is_invalid_andWeIndexfield_with_keyword() throws Exception { + try { + Set<String> indices = new HashSet<String>(); + indices.add("invalid 1"); + GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); + doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); + when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("any field", new HashMap()); + builder.endObject(); + BytesReference bytesReference = BytesReference.bytes(builder); + FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); + Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); + mapBuilder.put("data.any field", mappingMetaData); + Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); + mappingBuilder.put("any index 1", mapBuilder); + mappingBuilder.put("any index 2", mapBuilder); + Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); + mapping.put("indices 1", mappingBuilder); + when(getFieldMappingsResponse.mappings()).thenReturn(mapping); + doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); + BulkByScrollResponse response = mock(BulkByScrollResponse.class); + doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); + when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + + this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field"); + } catch (Exception e) { + throw e; + } + } + + @Test(expected = AppException.class) + public void should_throw_exception_if_type_of_index_is_invalid_andWeIndexfield_with_keyword() throws Exception { + try { + Set<String> indices = new HashSet<String>(); + indices.add("indices 1"); + GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); + doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); + when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("any field", new HashMap()); + builder.endObject(); + BytesReference bytesReference = BytesReference.bytes(builder); + FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); + Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); + mapBuilder.put("data.any field", mappingMetaData); + Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); + mappingBuilder.put("any index 1", mapBuilder); + mappingBuilder.put("any index 2", mapBuilder); + Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); + mapping.put("indices 1", mappingBuilder); + when(getFieldMappingsResponse.mappings()).thenReturn(mapping); + doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); + BulkByScrollResponse response = mock(BulkByScrollResponse.class); + doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); + when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field invalid"); + } catch (Exception e) { + throw e; + } + } + + @Test(expected = AppException.class) + public void should_throw_exception_if_elastic_search_failedToFetch_andWeIndexfield_with_keyword() throws Exception { + try { + + Set<String> indices = new HashSet<String>(); + indices.add("indices 1"); + indices.add("indices Invalid"); + GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); + doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); + when(this.indicesClient.getFieldMapping(any(), any())).thenThrow(new ElasticsearchException("")); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("any field", new HashMap()); + builder.endObject(); + BytesReference bytesReference = BytesReference.bytes(builder); + FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); + Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); + mapBuilder.put("data.any field", mappingMetaData); + Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); + mappingBuilder.put("any index 1", mapBuilder); + mappingBuilder.put("any index 2", mapBuilder); + Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); + mapping.put("indices 1", mappingBuilder); + when(getFieldMappingsResponse.mappings()).thenReturn(mapping); + doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); + BulkByScrollResponse response = mock(BulkByScrollResponse.class); + doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); + when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field"); + } catch (AppException e) { + throw e; + } + } + + @Test(expected = AppException.class) + public void should_throw_exception_when_elastic_failedToIndex_indices_field_with_keyword() { + try { + Set<String> indices = new HashSet<String>(); + indices.add("indices 1"); + indices.add("indices Invalid"); + GetFieldMappingsResponse getFieldMappingsResponse = mock(GetFieldMappingsResponse.class); + doReturn(this.indicesClient).when(this.restHighLevelClient).indices(); + when(this.indicesClient.getFieldMapping(any(), any())).thenReturn(getFieldMappingsResponse); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("any field", new HashMap()); + builder.endObject(); + BytesReference bytesReference = BytesReference.bytes(builder); + FieldMappingMetaData mappingMetaData = new FieldMappingMetaData(index, bytesReference); + Map<String, FieldMappingMetaData> mapBuilder = new HashMap<>(); + mapBuilder.put("data.any field", mappingMetaData); + Map<String, Map<String, FieldMappingMetaData>> mappingBuilder = new HashMap<>(); + mappingBuilder.put("any index 1", mapBuilder); + mappingBuilder.put("any index 2", mapBuilder); + Map<String, Map<String, Map<String, FieldMappingMetaData>>> mapping = new HashMap<>(); + mapping.put("indices 1", mappingBuilder); + when(getFieldMappingsResponse.mappings()).thenReturn(mapping); + doReturn(mappingResponse).when(this.indicesClient).putMapping(any(), any(RequestOptions.class)); + BulkByScrollResponse response = mock(BulkByScrollResponse.class); + doReturn(response).when(this.restHighLevelClient).updateByQuery(any(), any(RequestOptions.class)); + when(response.getBulkFailures()).thenReturn(new ArrayList<Failure>()); + when(this.indicesClient.putMapping(any(), any(RequestOptions.class))).thenThrow(new ElasticsearchException("")); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + this.sut.updateIndexMappingForIndicesOfSameType(indices,"any field"); + } catch (AppException e) { + throw e; + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + + @Test + public void should_returnDocumentMapping_givenValidIndexSchema() { + + try { + Map<String, Object> documentMapping = this.sut.getIndexMappingFromRecordSchema(this.indexSchema); + String documentMappingJson = new Gson().toJson(documentMapping); + assertEquals(this.mappingValid, documentMappingJson); + + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } +} diff --git a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerSchemaServiceTest.java b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerSchemaServiceTest.java index a1b47530ee22f7f3a5ed5f51cd4fbc3cc8c8006a..434e9bc803383e3034c65b4d8ea66fccd60fb25c 100644 --- a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerSchemaServiceTest.java +++ b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/IndexerSchemaServiceTest.java @@ -1,323 +1,400 @@ -// 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.indexer.service; - -import org.apache.http.HttpStatus; -import org.elasticsearch.client.RestHighLevelClient; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.indexer.OperationType; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.indexer.provider.interfaces.ISchemaCache; -import org.opengroup.osdu.core.common.model.indexer.IndexSchema; -import org.opengroup.osdu.core.common.model.http.RequestStatus; -import org.opengroup.osdu.core.common.search.IndicesService; -import org.opengroup.osdu.indexer.util.ElasticClientHandler; -import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.initMocks; -import static org.powermock.api.mockito.PowerMockito.mock; -import static org.powermock.api.mockito.PowerMockito.when; - -@RunWith(SpringRunner.class) -@PrepareForTest({RestHighLevelClient.class}) -public class IndexerSchemaServiceTest { - - private final String kind = "tenant:test:test:1.0.0"; - private final String emptySchema = null; - private final String someSchema = "{\"kind\":\"tenant:test:test:1.0.0\", \"schema\":[{\"path\":\"test-path\", \"kind\":\"tenant:test:test:1.0.0\"}]}"; - - @Mock - private JaxRsDpsLog log; - @Mock - private StorageService storageService; - @Mock - private ElasticClientHandler elasticClientHandler; - @Mock - private ElasticIndexNameResolver elasticIndexNameResolver; - @Mock - private IndexerMappingService mappingService; - @Mock - private IndicesService indicesService; - @Mock - private ISchemaCache schemaCache; - @InjectMocks - private IndexSchemaServiceImpl sut; - - @Before - public void setup() { - initMocks(this); - RestHighLevelClient restHighLevelClient = mock(RestHighLevelClient.class); - when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); - } - - @Test - public void should_returnNull_givenEmptySchema_getIndexerInputSchemaSchemaTest() throws Exception { - when(storageService.getStorageSchema(any())).thenReturn(emptySchema); - - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); - - Assert.assertNotNull(indexSchema); - } - - @Test - public void should_returnValidResponse_givenValidSchema_getIndexerInputSchemaTest() throws Exception { - when(storageService.getStorageSchema(any())).thenReturn(someSchema); - - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); - - Assert.assertEquals(kind, indexSchema.getKind()); - } - - @Test - public void should_returnValidResponse_givenValidSchemaWithCacheHit_getIndexerInputSchemaTest() throws Exception { - when(storageService.getStorageSchema(any())).thenReturn(someSchema); - when(this.schemaCache.get(kind + "_flattened")).thenReturn(someSchema); - - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); - - Assert.assertEquals(kind, indexSchema.getKind()); - } - - @Test - public void should_throw500_givenInvalidSchemaCacheHit_getIndexerInputSchemaTest() { - try { - String invalidSchema = "{}}"; - when(storageService.getStorageSchema(any())).thenReturn(invalidSchema); - - this.sut.getIndexerInputSchema(kind); - fail("Should throw exception"); - } catch (AppException e) { - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getError().getCode()); - Assert.assertEquals("An error has occurred while normalizing the schema.", e.getError().getMessage()); - } catch (Exception e) { - fail("Should not throw exception" + e.getMessage()); - } - } - - @Test - public void should_return_basic_schema_when_storage_returns_no_schema() { - IndexSchema returnedSchema = this.sut.getIndexerInputSchema(kind); - - assertNotNull(returnedSchema.getDataSchema()); - assertNotNull(returnedSchema); - assertEquals(kind, returnedSchema.getKind()); - } - - @Test - public void should_create_schema_when_storage_returns_valid_schema() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"startDate\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"endDate\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"type \"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"itemguid\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.mappingService, times(1)).getIndexMappingFromRecordSchema(any()); - verify(this.indicesService, times(1)).createIndex(any(), any(), any(), any(), any()); - verifyNoMoreInteractions(this.mappingService); - } - - @Test - public void should_merge_mapping_when_storage_returns_valid_schema() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"startDate\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.indicesService, times(0)).createIndex(any(), any(), any(), any(), any()); - verify(this.mappingService, times(1)).createMapping(any(), any(), any(), anyBoolean()); - verifyNoMoreInteractions(this.mappingService); - } - - @Test - public void should_throw_mapping_conflict_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_BAD_REQUEST, reason, "")); - - try { - this.sut.processSchemaMessages(schemaMessages); - } catch (AppException e){ - assertEquals(e.getError().getCode(), RequestStatus.SCHEMA_CONFLICT); - assertEquals(e.getError().getMessage(), "error creating or merging index mapping"); - assertEquals(e.getError().getReason(), reason); - } catch (Exception e) { - fail("Should not throw this exception " + e.getMessage()); - } - } - - @Test - public void should_throw_genericAppException_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_FORBIDDEN, reason, "blah")); - - try { - this.sut.processSchemaMessages(schemaMessages); - } catch (AppException e){ - assertEquals(e.getError().getCode(), HttpStatus.SC_FORBIDDEN); - assertEquals(e.getError().getMessage(), "blah"); - assertEquals(e.getError().getReason(), reason); - } catch (Exception e) { - fail("Should not throw this exception " + e.getMessage()); - } - } - - - @Test - public void should_log_and_do_nothing_when_storage_returns_invalid_schema() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.log).warning(eq("schema not found for kind: tenant1:avocet:completion:1.0.0")); - } - - @Test - public void should_invalidateCache_when_purge_schema_and_schema_found_in_cache() throws IOException { - String kind = "tenant1:avocet:completion:1.0.0"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.purge_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.schemaCache.get(kind)).thenReturn("schema"); - when(this.schemaCache.get(kind + "_flattened")).thenReturn("flattened schema"); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.schemaCache, times(2)).get(anyString()); - verify(this.schemaCache, times(2)).delete(anyString()); - } - - @Test - public void should_log_warning_when_purge_schema_and_schema_not_found_in_cache() throws IOException { - String kind = "tenant1:avocet:completion:1.0.0"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.purge_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.log).warning(eq(String.format("Kind: %s not found", kind))); - } -} +// 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.indexer.service; + +import org.apache.http.HttpStatus; +import org.elasticsearch.client.RestHighLevelClient; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.indexer.OperationType; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.indexer.provider.interfaces.ISchemaCache; +import org.opengroup.osdu.core.common.model.indexer.IndexSchema; +import org.opengroup.osdu.core.common.model.http.RequestStatus; +import org.opengroup.osdu.core.common.search.IndicesService; +import org.opengroup.osdu.indexer.util.ElasticClientHandler; +import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(SpringRunner.class) +@PrepareForTest({RestHighLevelClient.class}) +public class IndexerSchemaServiceTest { + + private final String kind = "tenant:test:test:1.0.0"; + private final String emptySchema = null; + private final String someSchema = "{\"kind\":\"tenant:test:test:1.0.0\", \"schema\":[{\"path\":\"test-path\", \"kind\":\"tenant:test:test:1.0.0\"}]}"; + + @Mock + private JaxRsDpsLog log; + @Mock + private StorageService storageService; + @Mock + private ElasticClientHandler elasticClientHandler; + @Mock + private ElasticIndexNameResolver elasticIndexNameResolver; + @Mock + private IndexerMappingService mappingService; + @Mock + private IndicesService indicesService; + @Mock + private ISchemaCache schemaCache; + @InjectMocks + private IndexSchemaServiceImpl sut; + + @Before + public void setup() { + initMocks(this); + RestHighLevelClient restHighLevelClient = mock(RestHighLevelClient.class); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + } + + @Test + public void should_returnNull_givenEmptySchema_getIndexerInputSchemaSchemaTest() throws Exception { + when(storageService.getStorageSchema(any())).thenReturn(emptySchema); + + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); + + Assert.assertNotNull(indexSchema); + } + + @Test + public void should_returnValidResponse_givenValidSchema_getIndexerInputSchemaTest() throws Exception { + when(storageService.getStorageSchema(any())).thenReturn(someSchema); + + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); + + Assert.assertEquals(kind, indexSchema.getKind()); + } + + @Test + public void should_returnValidResponse_givenValidSchemaWithCacheHit_getIndexerInputSchemaTest() throws Exception { + when(storageService.getStorageSchema(any())).thenReturn(someSchema); + when(this.schemaCache.get(kind + "_flattened")).thenReturn(someSchema); + + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); + + Assert.assertEquals(kind, indexSchema.getKind()); + } + + @Test + public void should_throw500_givenInvalidSchemaCacheHit_getIndexerInputSchemaTest() { + try { + String invalidSchema = "{}}"; + when(storageService.getStorageSchema(any())).thenReturn(invalidSchema); + + this.sut.getIndexerInputSchema(kind, false); + fail("Should throw exception"); + } catch (AppException e) { + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getError().getCode()); + Assert.assertEquals("An error has occurred while normalizing the schema.", e.getError().getMessage()); + } catch (Exception e) { + fail("Should not throw exception" + e.getMessage()); + } + } + + @Test + public void should_return_basic_schema_when_storage_returns_no_schema() { + IndexSchema returnedSchema = this.sut.getIndexerInputSchema(kind, false); + + assertNotNull(returnedSchema.getDataSchema()); + assertNotNull(returnedSchema); + assertEquals(kind, returnedSchema.getKind()); + } + + @Test + public void should_create_schema_when_storage_returns_valid_schema() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"startDate\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"endDate\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"type \"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"itemguid\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.mappingService, times(1)).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, times(1)).createIndex(any(), any(), any(), any(), any()); + verifyNoMoreInteractions(this.mappingService); + } + + @Test + public void should_merge_mapping_when_storage_returns_valid_schema() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"startDate\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.indicesService, times(0)).createIndex(any(), any(), any(), any(), any()); + verify(this.mappingService, times(1)).createMapping(any(), any(), any(), anyBoolean()); + verifyNoMoreInteractions(this.mappingService); + } + + @Test + public void should_throw_mapping_conflict_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_BAD_REQUEST, reason, "")); + + try { + this.sut.processSchemaMessages(schemaMessages); + } catch (AppException e) { + assertEquals(e.getError().getCode(), RequestStatus.SCHEMA_CONFLICT); + assertEquals(e.getError().getMessage(), "error creating or merging index mapping"); + assertEquals(e.getError().getReason(), reason); + } catch (Exception e) { + fail("Should not throw this exception " + e.getMessage()); + } + } + + @Test + public void should_throw_genericAppException_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_FORBIDDEN, reason, "blah")); + + try { + this.sut.processSchemaMessages(schemaMessages); + } catch (AppException e) { + assertEquals(e.getError().getCode(), HttpStatus.SC_FORBIDDEN); + assertEquals(e.getError().getMessage(), "blah"); + assertEquals(e.getError().getReason(), reason); + } catch (Exception e) { + fail("Should not throw this exception " + e.getMessage()); + } + } + + @Test + public void should_log_and_do_nothing_when_storage_returns_invalid_schema() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.log).warning(eq("schema not found for kind: tenant1:avocet:completion:1.0.0")); + } + + @Test + public void should_invalidateCache_when_purge_schema_and_schema_found_in_cache() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.purge_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.schemaCache.get(kind)).thenReturn("schema"); + when(this.schemaCache.get(kind + "_flattened")).thenReturn("flattened schema"); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.schemaCache, times(2)).get(anyString()); + verify(this.schemaCache, times(2)).delete(anyString()); + } + + @Test + public void should_log_warning_when_purge_schema_and_schema_not_found_in_cache() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.purge_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.log).warning(eq(String.format("Kind: %s not found", kind))); + } + + @Test + public void should_sync_schema_with_storage() throws Exception { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.indicesService.deleteIndex(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.syncIndexMappingWithStorageSchema(kind); + + verify(this.mappingService, times(1)).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, times(1)).isIndexExist(any(), any()); + verify(this.indicesService, times(1)).deleteIndex(any(), any()); + verify(this.indicesService, times(1)).createIndex(any(), any(), any(), any(), any()); + verifyNoMoreInteractions(this.mappingService); + } + + @Test + public void should_throw_exception_while_snapshot_running_sync_schema_with_storage() throws Exception { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.indicesService.deleteIndex(any(), any())).thenThrow(new AppException(HttpStatus.SC_CONFLICT, "Index deletion error", "blah")); + + try { + this.sut.syncIndexMappingWithStorageSchema(kind); + } catch (AppException e) { + assertEquals(e.getError().getCode(), HttpStatus.SC_CONFLICT); + assertEquals(e.getError().getMessage(), "blah"); + assertEquals(e.getError().getReason(), "Index deletion error"); + } catch (Exception e) { + fail("Should not throw this exception " + e.getMessage()); + } + + verify(this.indicesService, times(1)).isIndexExist(any(), any()); + verify(this.indicesService, times(1)).deleteIndex(any(), any()); + verify(this.mappingService, never()).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, never()).createIndex(any(), any(), any(), any(), any()); + } + + @Test + public void should_return_true_while_if_forceClean_requested() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + + assertTrue(this.sut.isStorageSchemaSyncRequired(kind, true)); + } + + @Test + public void should_return_true_while_if_forceClean_notRequested_and_indexNotFound() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); + + assertTrue(this.sut.isStorageSchemaSyncRequired(kind, false)); + } + + @Test + public void should_return_false_while_if_forceClean_notRequested_and_indexExist() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + + assertFalse(this.sut.isStorageSchemaSyncRequired(kind, false)); + } +} diff --git a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/ReindexServiceTest.java b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/ReindexServiceTest.java index cec115db0944b7d8280250904c89344207a5724c..bf2465ed8bc18d41f66d373e4e048e050e3b53ca 100644 --- a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/ReindexServiceTest.java +++ b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/ReindexServiceTest.java @@ -1,142 +1,151 @@ -// 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.indexer.service; - - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; -import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; -import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.*; - -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.MockitoAnnotations.initMocks; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.when; - -@RunWith(SpringRunner.class) -public class ReindexServiceTest { - - private final String cursor = "100"; - - private final String correlationId = UUID.randomUUID().toString(); - - @Mock - private StorageService storageService; - - @Mock - private Map<String, String> httpHeaders; - @Mock - private IRequestInfo requestInfo; - @Mock - private IndexerQueueTaskBuilder indexerQueueTaskBuilder; - @Mock - private JaxRsDpsLog log; - @InjectMocks - private ReindexServiceImpl sut; - - private RecordReindexRequest recordReindexRequest; - private RecordQueryResponse recordQueryResponse; - - @Before - public void setup() { - initMocks(this); - - mockStatic(UUID.class); - - recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor(cursor).build(); - recordQueryResponse = new RecordQueryResponse(); - - httpHeaders = new HashMap<>(); - httpHeaders.put(DpsHeaders.AUTHORIZATION, "testAuth"); - httpHeaders.put(DpsHeaders.CORRELATION_ID, correlationId); - DpsHeaders standardHeaders = DpsHeaders.createFromMap(httpHeaders); - when(requestInfo.getHeaders()).thenReturn(standardHeaders); - when(requestInfo.getHeadersMapWithDwdAuthZ()).thenReturn(httpHeaders); - when(requestInfo.getHeadersWithDwdAuthZ()).thenReturn(standardHeaders); - } - - @Test - public void should_returnNull_givenNullResponseResult_reIndexRecordsTest() { - try { - recordQueryResponse.setResults(null); - when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); - - String response = sut.reindexRecords(recordReindexRequest); - - Assert.assertNull(response); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test - public void should_returnNull_givenEmptyResponseResult_reIndexRecordsTest() { - try { - recordQueryResponse.setResults(new ArrayList<>()); - when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); - - String response = sut.reindexRecords(recordReindexRequest); - - Assert.assertNull(response); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test - public void should_returnRecordQueryRequestPayload_givenValidResponseResult_reIndexRecordsTest() { - try { - recordQueryResponse.setCursor(cursor); - List<String> results = new ArrayList<>(); - results.add("test1"); - recordQueryResponse.setResults(results); - when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); - - String taskQueuePayload = sut.reindexRecords(recordReindexRequest); - - Assert.assertEquals("{\"kind\":\"tenant:test:test:1.0.0\",\"cursor\":\"100\"}", taskQueuePayload); - } catch (Exception e) { - fail("Should not throw exception" + e.getMessage()); - } - } - - @Test - public void should_returnRecordChangedMessage_givenValidResponseResult_reIndexRecordsTest() { - try { - List<String> results = new ArrayList<>(); - results.add("test1"); - recordQueryResponse.setResults(results); - when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); - - String taskQueuePayload = sut.reindexRecords(recordReindexRequest); - - Assert.assertEquals(String.format("{\"data\":\"[{\\\"id\\\":\\\"test1\\\",\\\"kind\\\":\\\"tenant:test:test:1.0.0\\\",\\\"op\\\":\\\"create\\\"}]\",\"attributes\":{\"correlation-id\":\"%s\"}}", correlationId), taskQueuePayload); - } catch (Exception e) { - fail("Should not throw exception" + e.getMessage()); - } - } -} +// 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.indexer.service; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; +import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; +import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; +import org.opengroup.osdu.core.common.search.Config; +import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.*; + +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.MockitoAnnotations.initMocks; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(SpringRunner.class) +@PrepareForTest({Config.class}) +public class ReindexServiceTest { + + private final String cursor = "100"; + + private final String correlationId = UUID.randomUUID().toString(); + + @Mock + private StorageService storageService; + @Mock + private IRequestInfo requestInfo; + @Mock + private IndexerQueueTaskBuilder indexerQueueTaskBuilder; + @Mock + private JaxRsDpsLog log; + @InjectMocks + private ReindexServiceImpl sut; + + private RecordReindexRequest recordReindexRequest; + private RecordQueryResponse recordQueryResponse; + + private Map<String, String> httpHeaders; + + @Before + public void setup() { + initMocks(this); + + mockStatic(UUID.class); + + recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor(cursor).build(); + recordQueryResponse = new RecordQueryResponse(); + + httpHeaders = new HashMap<>(); + httpHeaders.put(DpsHeaders.AUTHORIZATION, "testAuth"); + httpHeaders.put(DpsHeaders.CORRELATION_ID, correlationId); + DpsHeaders standardHeaders = DpsHeaders.createFromMap(httpHeaders); + when(requestInfo.getHeaders()).thenReturn(standardHeaders); + when(requestInfo.getHeadersMapWithDwdAuthZ()).thenReturn(httpHeaders); + when(requestInfo.getHeadersWithDwdAuthZ()).thenReturn(standardHeaders); + } + + @Test + public void should_returnNull_givenNullResponseResult_reIndexRecordsTest() { + try { + recordQueryResponse.setResults(null); + when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); + + String response = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertNull(response); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test + public void should_returnNull_givenEmptyResponseResult_reIndexRecordsTest() { + try { + recordQueryResponse.setResults(new ArrayList<>()); + when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); + + String response = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertNull(response); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + @Ignore + @Test + public void should_returnRecordQueryRequestPayload_givenValidResponseResult_reIndexRecordsTest() { + try { + recordQueryResponse.setCursor(cursor); + List<String> results = new ArrayList<>(); + results.add("test1"); + recordQueryResponse.setResults(results); + + mockStatic(Config.class); + when(Config.getStorageRecordsBatchSize()).thenReturn(1); + + when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); + + String taskQueuePayload = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertEquals("{\"kind\":\"tenant:test:test:1.0.0\",\"cursor\":\"100\"}", taskQueuePayload); + } catch (Exception e) { + fail("Should not throw exception" + e.getMessage()); + } + } + + @Test + public void should_returnRecordChangedMessage_givenValidResponseResult_reIndexRecordsTest() { + try { + List<String> results = new ArrayList<>(); + results.add("test1"); + recordQueryResponse.setResults(results); + when(storageService.getRecordsByKind(any())).thenReturn(recordQueryResponse); + + String taskQueuePayload = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertEquals(String.format("{\"data\":\"[{\\\"id\\\":\\\"test1\\\",\\\"kind\\\":\\\"tenant:test:test:1.0.0\\\",\\\"op\\\":\\\"create\\\"}]\",\"attributes\":{\"correlation-id\":\"%s\"}}", correlationId), taskQueuePayload); + } catch (Exception e) { + fail("Should not throw exception" + e.getMessage()); + } + } +} diff --git a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/StorageServiceTest.java b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/StorageServiceTest.java index fb5461bb31f90ec9ed85ee23fa305399adac66d4..e868e50c842262a4eba35f2fa49cad1c2aae3cf6 100644 --- a/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/StorageServiceTest.java +++ b/provider/indexer-gcp/src/test/java/org/opengroup/osdu/indexer/service/StorageServiceTest.java @@ -1,213 +1,248 @@ -// 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.indexer.service; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.indexer.JobStatus; -import org.opengroup.osdu.core.common.model.indexer.RecordInfo; -import org.opengroup.osdu.core.common.model.http.HttpResponse; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; -import org.opengroup.osdu.core.common.http.IUrlFetchService; -import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; -import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; -import org.opengroup.osdu.core.common.model.indexer.Records; -import org.springframework.http.HttpStatus; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.util.ReflectionTestUtils; - -import java.lang.reflect.Type; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import static org.junit.Assert.*; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.powermock.api.mockito.PowerMockito.when; - -@RunWith(SpringRunner.class) -public class StorageServiceTest { - - @Mock - private IUrlFetchService urlFetchService; - @Mock - private JobStatus jobStatus; - @Mock - private JaxRsDpsLog log; - @Mock - private IRequestInfo requestInfo; - @InjectMocks - private StorageServiceImpl sut; - - private List<String> ids; - - @Before - public void setup() { - - String recordChangedMessages = "[{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"purge\"}," + - "{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"create\"}]"; - - when(this.requestInfo.getHeadersMap()).thenReturn(new HashMap<>()); - when(this.requestInfo.getHeaders()).thenReturn(new DpsHeaders()); - - Type listType = new TypeToken<List<RecordInfo>>() {}.getType(); - - List<RecordInfo> msgs = (new Gson()).fromJson(recordChangedMessages, listType); - jobStatus.initialize(msgs); - ids = Arrays.asList("tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465", "tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465"); - - ReflectionTestUtils.setField(this.sut, "STORAGE_RECORDS_BATCH_SIZE", "20"); - } - - @Test - public void should_return404_givenNullData_getValidStorageRecordsTest() throws URISyntaxException { - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(null); - - when(this.urlFetchService.sendRequest(any(), any(), any(), any(), any())).thenReturn(httpResponse); - - should_return404_getValidStorageRecordsTest(); - } - - @Test - public void should_return404_givenEmptyData_getValidStorageRecordsTest() throws URISyntaxException { - - String emptyDataFromStorage = "{\"records\":[],\"notFound\":[]}"; - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(emptyDataFromStorage); - - when(this.urlFetchService.sendRequest(any(), any(), any(), any(), any())).thenReturn(httpResponse); - - should_return404_getValidStorageRecordsTest(); - } - - @Test - public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsTest() throws URISyntaxException { - - String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"], \"conversionStatuses\": []}"; - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); - - when(this.urlFetchService.sendRequest(any(), any(), any(), any(), any())).thenReturn(httpResponse); - Records storageRecords = this.sut.getStorageRecords(ids); - - assertEquals(1, storageRecords.getRecords().size()); - } - - @Test - public void should_returnValidResponse_givenValidRecordQueryRequest_getRecordListByKind() throws Exception { - - RecordReindexRequest recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor("100").build(); - - HttpResponse httpResponse = new HttpResponse(); - httpResponse.setBody(new Gson().toJson(recordReindexRequest, RecordReindexRequest.class)); - - when(this.urlFetchService.sendRequest(any(), any(), any(), any(), any())).thenReturn(httpResponse); - - RecordQueryResponse recordQueryResponse = this.sut.getRecordsByKind(recordReindexRequest); - - assertEquals("100", recordQueryResponse.getCursor()); - assertNull(recordQueryResponse.getResults()); - } - - @Test - public void should_returnValidResponse_givenValidKind_getSchemaByKind() throws Exception { - - String validSchemaFromStorage = "{" + - " \"kind\": \"tenant:test:test:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"msg\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"references.entity\"," + - " \"kind\": \"string\"" + - " }" + - " ]," + - " \"ext\": null" + - "}"; - String kind = "tenant:test:test:1.0.0"; - - HttpResponse httpResponse = new HttpResponse(); - httpResponse.setResponseCode(HttpStatus.OK.value()); - httpResponse.setBody(validSchemaFromStorage); - - when(this.urlFetchService.sendRequest(any(), any(), any(), any(), any())).thenReturn(httpResponse); - - String recordSchemaResponse = this.sut.getStorageSchema(kind); - - assertNotNull(recordSchemaResponse); - } - - @Test - public void should_returnNullResponse_givenAbsentKind_getSchemaByKind() throws Exception { - - String kind = "tenant:test:test:1.0.0"; - - HttpResponse httpResponse = new HttpResponse(); - httpResponse.setResponseCode(HttpStatus.NOT_FOUND.value()); - - when(this.urlFetchService.sendRequest(any(), any(), any(), any(), any())).thenReturn(httpResponse); - - String recordSchemaResponse = this.sut.getStorageSchema(kind); - - assertNull(recordSchemaResponse); - } - - @Test - public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsWithInvalidConversionTest() throws URISyntaxException { - - String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"],\"conversionStatuses\": [{\"id\":\"testid\",\"status\":\"ERROR\",\"errors\":[\"conversion error occured\"] } ]}"; - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); - - when(this.urlFetchService.sendRequest(any(), any(), any(), any(), any())).thenReturn(httpResponse); - Records storageRecords = this.sut.getStorageRecords(ids); - - assertEquals(1, storageRecords.getRecords().size()); - - assertEquals(1, storageRecords.getConversionStatuses().get(0).getErrors().size()); - - assertEquals("conversion error occured", storageRecords.getConversionStatuses().get(0).getErrors().get(0)); - } - - private void should_return404_getValidStorageRecordsTest() { - try { - this.sut.getStorageRecords(ids); - fail("Should throw exception"); - } catch (AppException e) { - assertEquals(HttpStatus.NOT_FOUND.value(), e.getError().getCode()); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } -} +// 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.indexer.service; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.indexer.IndexingStatus; +import org.opengroup.osdu.core.common.model.indexer.JobStatus; +import org.opengroup.osdu.core.common.model.indexer.RecordInfo; +import org.opengroup.osdu.core.common.model.http.HttpResponse; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; +import org.opengroup.osdu.core.common.http.IUrlFetchService; +import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; +import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; +import org.opengroup.osdu.core.common.model.indexer.Records; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import java.lang.reflect.Type; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(SpringRunner.class) +public class StorageServiceTest { + + @Mock + private IUrlFetchService urlFetchService; + @Mock + private JobStatus jobStatus; + @Mock + private JaxRsDpsLog log; + @Mock + private IRequestInfo requestInfo; + @InjectMocks + private StorageServiceImpl sut; + + private List<String> ids; + private static final String RECORD_ID1 = "tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465"; + private static final String RECORDS_ID2 = "tenant1:doc:15e790a69beb4d789b1f979e2af2e813"; + + @Before + public void setup() { + + String recordChangedMessages = "[{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"purge\"}," + + "{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"create\"}]"; + + when(this.requestInfo.getHeadersMap()).thenReturn(new HashMap<>()); + when(this.requestInfo.getHeaders()).thenReturn(new DpsHeaders()); + + Type listType = new TypeToken<List<RecordInfo>>() {}.getType(); + + List<RecordInfo> msgs = (new Gson()).fromJson(recordChangedMessages, listType); + jobStatus.initialize(msgs); + ids = Arrays.asList(RECORD_ID1, RECORDS_ID2); + + ReflectionTestUtils.setField(this.sut, "STORAGE_RECORDS_BATCH_SIZE", "20"); + } + + @Test + public void should_return404_givenNullData_getValidStorageRecordsTest() throws URISyntaxException { + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(null); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + should_return404_getValidStorageRecordsTest(); + } + + @Test + public void should_return404_givenEmptyData_getValidStorageRecordsTest() throws URISyntaxException { + + String emptyDataFromStorage = "{\"records\":[],\"notFound\":[]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(emptyDataFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + should_return404_getValidStorageRecordsTest(); + } + + @Test + public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsTest() throws URISyntaxException { + + String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"], \"conversionStatuses\": []}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(ids); + + assertEquals(1, storageRecords.getRecords().size()); + } + + @Test + public void should_logMissingRecord_given_storageMissedRecords() throws URISyntaxException { + + String validDataFromStorage = "{\"records\":[{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(ids); + + assertEquals(1, storageRecords.getRecords().size()); + verify(this.jobStatus).addOrUpdateRecordStatus(singletonList(RECORDS_ID2), IndexingStatus.FAIL, HttpStatus.NOT_FOUND.value(), "Partial response received from Storage service - missing records", "Partial response received from Storage service: tenant1:doc:15e790a69beb4d789b1f979e2af2e813"); + } + + @Test + public void should_returnValidJobStatus_givenFailedUnitsConversion_processRecordChangedMessageTest() throws URISyntaxException { + String validDataFromStorage = "{\"records\":[{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[],\"conversionStatuses\":[{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\",\"status\":\"ERROR\",\"errors\":[\"crs conversion failed\"]}]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(singletonList(RECORDS_ID2)); + + assertEquals(1, storageRecords.getRecords().size()); + verify(this.jobStatus).addOrUpdateRecordStatus(RECORDS_ID2, IndexingStatus.WARN, HttpStatus.BAD_REQUEST.value(), "crs conversion failed", String.format("record-id: %s | %s", "tenant1:doc:15e790a69beb4d789b1f979e2af2e813", "crs conversion failed")); + } + + @Test + public void should_returnValidResponse_givenValidRecordQueryRequest_getRecordListByKind() throws Exception { + + RecordReindexRequest recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor("100").build(); + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setBody(new Gson().toJson(recordReindexRequest, RecordReindexRequest.class)); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + RecordQueryResponse recordQueryResponse = this.sut.getRecordsByKind(recordReindexRequest); + + assertEquals("100", recordQueryResponse.getCursor()); + assertNull(recordQueryResponse.getResults()); + } + + @Test + public void should_returnValidResponse_givenValidKind_getSchemaByKind() throws Exception { + + String validSchemaFromStorage = "{" + + " \"kind\": \"tenant:test:test:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"msg\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"references.entity\"," + + " \"kind\": \"string\"" + + " }" + + " ]," + + " \"ext\": null" + + "}"; + String kind = "tenant:test:test:1.0.0"; + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setResponseCode(HttpStatus.OK.value()); + httpResponse.setBody(validSchemaFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + String recordSchemaResponse = this.sut.getStorageSchema(kind); + + assertNotNull(recordSchemaResponse); + } + + @Test + public void should_returnNullResponse_givenAbsentKind_getSchemaByKind() throws Exception { + + String kind = "tenant:test:test:1.0.0"; + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setResponseCode(HttpStatus.NOT_FOUND.value()); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + String recordSchemaResponse = this.sut.getStorageSchema(kind); + + assertNull(recordSchemaResponse); + } + + @Test + public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsWithInvalidConversionTest() throws URISyntaxException { + + String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"],\"conversionStatuses\": [{\"id\":\"testid\",\"status\":\"ERROR\",\"errors\":[\"conversion error occurred\"] } ]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(ids); + + assertEquals(1, storageRecords.getRecords().size()); + + assertEquals(1, storageRecords.getConversionStatuses().get(0).getErrors().size()); + + assertEquals("conversion error occurred", storageRecords.getConversionStatuses().get(0).getErrors().get(0)); + } + + private void should_return404_getValidStorageRecordsTest() { + try { + this.sut.getStorageRecords(ids); + fail("Should throw exception"); + } catch (AppException e) { + assertEquals(HttpStatus.NOT_FOUND.value(), e.getError().getCode()); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } +} diff --git a/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/IndexerSchemaServiceTest.java b/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/IndexerSchemaServiceTest.java index 25dd2ed446ebc1cb107a0149c139db46705569f2..db2eb01d13ab616671c3a6e855d13c67a4e7ed94 100644 --- a/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/IndexerSchemaServiceTest.java +++ b/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/IndexerSchemaServiceTest.java @@ -1,329 +1,406 @@ -// 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.indexer.ibm.service; - -import org.apache.http.HttpStatus; -import org.elasticsearch.client.RestHighLevelClient; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.opengroup.osdu.core.common.model.indexer.IndexSchema; -import org.opengroup.osdu.core.common.model.indexer.OperationType; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.indexer.provider.interfaces.ISchemaCache; -import org.opengroup.osdu.indexer.service.IndexSchemaServiceImpl; -import org.opengroup.osdu.indexer.service.IndexerMappingService; -import org.opengroup.osdu.indexer.service.StorageService; -import org.opengroup.osdu.core.common.model.http.RequestStatus; -import org.opengroup.osdu.core.common.search.IndicesService; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.indexer.util.ElasticClientHandler; -import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.initMocks; -import static org.powermock.api.mockito.PowerMockito.mock; -import static org.powermock.api.mockito.PowerMockito.when; - -@Ignore -@RunWith(SpringRunner.class) -@PrepareForTest({RestHighLevelClient.class}) -public class IndexerSchemaServiceTest { - - private final String kind = "tenant:test:test:1.0.0"; - private final String emptySchema = null; - private final String someSchema = "{\"kind\":\"tenant:test:test:1.0.0\", \"schema\":[{\"path\":\"test-path\", \"kind\":\"tenant:test:test:1.0.0\"}]}"; - - @Mock - private JaxRsDpsLog log; - @Mock - private StorageService storageService; - @Mock - private ElasticClientHandler elasticClientHandler; - @Mock - private ElasticIndexNameResolver elasticIndexNameResolver; - @Mock - private IndexerMappingService mappingService; - @Mock - private IndicesService indicesService; - @Mock - private ISchemaCache schemaCache; - @InjectMocks - private IndexSchemaServiceImpl sut; - - @Before - public void setup() { - initMocks(this); - RestHighLevelClient restHighLevelClient = mock(RestHighLevelClient.class); - when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); - } - - @Test - public void should_returnNull_givenEmptySchema_getIndexerInputSchemaSchemaTest() throws Exception { - when(storageService.getStorageSchema(any())).thenReturn(emptySchema); - - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); - - Assert.assertNotNull(indexSchema); - } - - @Test - public void should_returnValidResponse_givenValidSchema_getIndexerInputSchemaTest() throws Exception { - when(storageService.getStorageSchema(any())).thenReturn(someSchema); - - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); - - Assert.assertEquals(kind, indexSchema.getKind()); - } - - @Test - public void should_returnValidResponse_givenValidSchemaWithCacheHit_getIndexerInputSchemaTest() throws Exception { - when(storageService.getStorageSchema(any())).thenReturn(someSchema); - when(this.schemaCache.get(kind + "_flattened")).thenReturn(someSchema); - - IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind); - - Assert.assertEquals(kind, indexSchema.getKind()); - } - - @Test - public void should_throw500_givenInvalidSchemaCacheHit_getIndexerInputSchemaTest() { - try { - String invalidSchema = "{}}"; - when(storageService.getStorageSchema(any())).thenReturn(invalidSchema); - - this.sut.getIndexerInputSchema(kind); - fail("Should throw exception"); - } catch (AppException e) { - Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getError().getCode()); - Assert.assertEquals("An error has occurred while normalizing the schema.", e.getError().getMessage()); - } catch (Exception e) { - fail("Should not throw exception" + e.getMessage()); - } - } - - @Test - public void should_return_basic_schema_when_storage_returns_no_schema() { - IndexSchema returnedSchema = this.sut.getIndexerInputSchema(kind); - - assertNotNull(returnedSchema.getDataSchema()); - assertNotNull(returnedSchema); - assertEquals(kind, returnedSchema.getKind()); - } - - @Test - public void should_create_schema_when_storage_returns_valid_schema() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"startDate\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"endDate\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"type \"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"itemguid\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.mappingService, times(1)).getIndexMappingFromRecordSchema(any()); - verify(this.indicesService, times(1)).createIndex(any(), any(), any(), any(), any()); - verifyNoMoreInteractions(this.mappingService); - } - - @Test - public void should_merge_mapping_when_storage_returns_valid_schema() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"startDate\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.indicesService, times(0)).createIndex(any(), any(), any(), any(), any()); - verify(this.mappingService, times(1)).createMapping(any(), any(), any(), anyBoolean()); - verifyNoMoreInteractions(this.mappingService); - } - - @Test - public void should_throw_mapping_conflict_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_BAD_REQUEST, reason, "")); - - try { - this.sut.processSchemaMessages(schemaMessages); - } catch (AppException e){ - assertEquals(e.getError().getCode(), RequestStatus.SCHEMA_CONFLICT); - assertEquals(e.getError().getMessage(), "error creating or merging index mapping"); - assertEquals(e.getError().getReason(), reason); - } catch (Exception e) { - fail("Should not throw this exception " + e.getMessage()); - } - } - - @Test - public void should_throw_genericAppException_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"status\"," + - " \"kind\": \"string\"" + - " }" + - " ]" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_FORBIDDEN, reason, "blah")); - - try { - this.sut.processSchemaMessages(schemaMessages); - } catch (AppException e){ - assertEquals(e.getError().getCode(), HttpStatus.SC_FORBIDDEN); - assertEquals(e.getError().getMessage(), "blah"); - assertEquals(e.getError().getReason(), reason); - } catch (Exception e) { - fail("Should not throw this exception " + e.getMessage()); - } - } - - - @Test - public void should_log_and_do_nothing_when_storage_returns_invalid_schema() throws IOException, URISyntaxException { - String kind = "tenant1:avocet:completion:1.0.0"; - String storageSchema = "{" + - " \"kind\": \"tenant1:avocet:completion:1.0.0\"" + - "}"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.create_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.schemaCache.get(kind)).thenReturn(null); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.log).warning(eq("schema not found for kind: tenant1:avocet:completion:1.0.0")); - } - - @Test - public void should_invalidateCache_when_purge_schema_and_schema_found_in_cache() throws IOException { - String kind = "tenant1:avocet:completion:1.0.0"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.purge_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); - when(this.schemaCache.get(kind)).thenReturn("schema"); - when(this.schemaCache.get(kind + "_flattened")).thenReturn("flattened schema"); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.schemaCache, times(2)).get(anyString()); - verify(this.schemaCache, times(2)).delete(anyString()); - } - - @Test - public void should_log_warning_when_purge_schema_and_schema_not_found_in_cache() throws IOException { - String kind = "tenant1:avocet:completion:1.0.0"; - Map<String, OperationType> schemaMessages = new HashMap<>(); - schemaMessages.put(kind, OperationType.purge_schema); - - when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); - when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); - - this.sut.processSchemaMessages(schemaMessages); - - verify(this.log).warning(eq(String.format("Kind: %s not found", kind))); - } -} +// 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.indexer.ibm.service; + +import org.apache.http.HttpStatus; +import org.elasticsearch.client.RestHighLevelClient; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.model.indexer.IndexSchema; +import org.opengroup.osdu.core.common.model.indexer.OperationType; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.indexer.provider.interfaces.ISchemaCache; +import org.opengroup.osdu.indexer.service.IndexSchemaServiceImpl; +import org.opengroup.osdu.indexer.service.IndexerMappingService; +import org.opengroup.osdu.indexer.service.StorageService; +import org.opengroup.osdu.core.common.model.http.RequestStatus; +import org.opengroup.osdu.core.common.search.IndicesService; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.indexer.util.ElasticClientHandler; +import org.opengroup.osdu.core.common.search.ElasticIndexNameResolver; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; + +@Ignore +@RunWith(SpringRunner.class) +@PrepareForTest({RestHighLevelClient.class}) +public class IndexerSchemaServiceTest { + + private final String kind = "tenant:test:test:1.0.0"; + private final String emptySchema = null; + private final String someSchema = "{\"kind\":\"tenant:test:test:1.0.0\", \"schema\":[{\"path\":\"test-path\", \"kind\":\"tenant:test:test:1.0.0\"}]}"; + + @Mock + private JaxRsDpsLog log; + @Mock + private StorageService storageService; + @Mock + private ElasticClientHandler elasticClientHandler; + @Mock + private ElasticIndexNameResolver elasticIndexNameResolver; + @Mock + private IndexerMappingService mappingService; + @Mock + private IndicesService indicesService; + @Mock + private ISchemaCache schemaCache; + @InjectMocks + private IndexSchemaServiceImpl sut; + + @Before + public void setup() { + initMocks(this); + RestHighLevelClient restHighLevelClient = mock(RestHighLevelClient.class); + when(elasticClientHandler.createRestClient()).thenReturn(restHighLevelClient); + } + + @Test + public void should_returnNull_givenEmptySchema_getIndexerInputSchemaSchemaTest() throws Exception { + when(storageService.getStorageSchema(any())).thenReturn(emptySchema); + + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); + + Assert.assertNotNull(indexSchema); + } + + @Test + public void should_returnValidResponse_givenValidSchema_getIndexerInputSchemaTest() throws Exception { + when(storageService.getStorageSchema(any())).thenReturn(someSchema); + + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); + + Assert.assertEquals(kind, indexSchema.getKind()); + } + + @Test + public void should_returnValidResponse_givenValidSchemaWithCacheHit_getIndexerInputSchemaTest() throws Exception { + when(storageService.getStorageSchema(any())).thenReturn(someSchema); + when(this.schemaCache.get(kind + "_flattened")).thenReturn(someSchema); + + IndexSchema indexSchema = this.sut.getIndexerInputSchema(kind, false); + + Assert.assertEquals(kind, indexSchema.getKind()); + } + + @Test + public void should_throw500_givenInvalidSchemaCacheHit_getIndexerInputSchemaTest() { + try { + String invalidSchema = "{}}"; + when(storageService.getStorageSchema(any())).thenReturn(invalidSchema); + + this.sut.getIndexerInputSchema(kind, false); + fail("Should throw exception"); + } catch (AppException e) { + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getError().getCode()); + Assert.assertEquals("An error has occurred while normalizing the schema.", e.getError().getMessage()); + } catch (Exception e) { + fail("Should not throw exception" + e.getMessage()); + } + } + + @Test + public void should_return_basic_schema_when_storage_returns_no_schema() { + IndexSchema returnedSchema = this.sut.getIndexerInputSchema(kind, false); + + assertNotNull(returnedSchema.getDataSchema()); + assertNotNull(returnedSchema); + assertEquals(kind, returnedSchema.getKind()); + } + + @Test + public void should_create_schema_when_storage_returns_valid_schema() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"startDate\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"endDate\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"type \"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"itemguid\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.mappingService, times(1)).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, times(1)).createIndex(any(), any(), any(), any(), any()); + verifyNoMoreInteractions(this.mappingService); + } + + @Test + public void should_merge_mapping_when_storage_returns_valid_schema() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"startDate\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.indicesService, times(0)).createIndex(any(), any(), any(), any(), any()); + verify(this.mappingService, times(1)).createMapping(any(), any(), any(), anyBoolean()); + verifyNoMoreInteractions(this.mappingService); + } + + @Test + public void should_throw_mapping_conflict_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_BAD_REQUEST, reason, "")); + + try { + this.sut.processSchemaMessages(schemaMessages); + } catch (AppException e) { + assertEquals(e.getError().getCode(), RequestStatus.SCHEMA_CONFLICT); + assertEquals(e.getError().getMessage(), "error creating or merging index mapping"); + assertEquals(e.getError().getReason(), reason); + } catch (Exception e) { + fail("Should not throw this exception " + e.getMessage()); + } + } + + @Test + public void should_throw_genericAppException_when_elastic_backend_cannot_process_schema_changes() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String reason = String.format("Could not create type mapping %s/completion.", kind.replace(":", "-")); + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + when(this.mappingService.createMapping(any(), any(), any(), anyBoolean())).thenThrow(new AppException(HttpStatus.SC_FORBIDDEN, reason, "blah")); + + try { + this.sut.processSchemaMessages(schemaMessages); + } catch (AppException e) { + assertEquals(e.getError().getCode(), HttpStatus.SC_FORBIDDEN); + assertEquals(e.getError().getMessage(), "blah"); + assertEquals(e.getError().getReason(), reason); + } catch (Exception e) { + fail("Should not throw this exception " + e.getMessage()); + } + } + + @Test + public void should_log_and_do_nothing_when_storage_returns_invalid_schema() throws IOException, URISyntaxException { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"" + + "}"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.create_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.log).warning(eq("schema not found for kind: tenant1:avocet:completion:1.0.0")); + } + + @Test + public void should_invalidateCache_when_purge_schema_and_schema_found_in_cache() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.purge_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.schemaCache.get(kind)).thenReturn("schema"); + when(this.schemaCache.get(kind + "_flattened")).thenReturn("flattened schema"); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.schemaCache, times(2)).get(anyString()); + verify(this.schemaCache, times(2)).delete(anyString()); + } + + @Test + public void should_log_warning_when_purge_schema_and_schema_not_found_in_cache() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + Map<String, OperationType> schemaMessages = new HashMap<>(); + schemaMessages.put(kind, OperationType.purge_schema); + + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); + + this.sut.processSchemaMessages(schemaMessages); + + verify(this.log).warning(eq(String.format("Kind: %s not found", kind))); + } + + @Test + public void should_sync_schema_with_storage() throws Exception { + String kind = "tenant1:avocet:completion:1.0.0"; + String storageSchema = "{" + + " \"kind\": \"tenant1:avocet:completion:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"status\"," + + " \"kind\": \"string\"" + + " }" + + " ]" + + "}"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.indicesService.deleteIndex(any(), any())).thenReturn(true); + when(this.storageService.getStorageSchema(kind)).thenReturn(storageSchema); + + this.sut.syncIndexMappingWithStorageSchema(kind); + + verify(this.mappingService, times(1)).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, times(1)).isIndexExist(any(), any()); + verify(this.indicesService, times(1)).deleteIndex(any(), any()); + verify(this.indicesService, times(1)).createIndex(any(), any(), any(), any(), any()); + verifyNoMoreInteractions(this.mappingService); + } + + @Test + public void should_throw_exception_while_snapshot_running_sync_schema_with_storage() throws Exception { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.schemaCache.get(kind)).thenReturn(null); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + when(this.indicesService.deleteIndex(any(), any())).thenThrow(new AppException(HttpStatus.SC_CONFLICT, "Index deletion error", "blah")); + + try { + this.sut.syncIndexMappingWithStorageSchema(kind); + } catch (AppException e) { + assertEquals(e.getError().getCode(), HttpStatus.SC_CONFLICT); + assertEquals(e.getError().getMessage(), "blah"); + assertEquals(e.getError().getReason(), "Index deletion error"); + } catch (Exception e) { + fail("Should not throw this exception " + e.getMessage()); + } + + verify(this.indicesService, times(1)).isIndexExist(any(), any()); + verify(this.indicesService, times(1)).deleteIndex(any(), any()); + verify(this.mappingService, never()).getIndexMappingFromRecordSchema(any()); + verify(this.indicesService, never()).createIndex(any(), any(), any(), any(), any()); + } + + @Test + public void should_return_true_while_if_forceClean_requested() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + + assertTrue(this.sut.isStorageSchemaSyncRequired(kind, true)); + } + + @Test + public void should_return_true_while_if_forceClean_notRequested_and_indexNotFound() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(false); + + assertTrue(this.sut.isStorageSchemaSyncRequired(kind, false)); + } + + @Test + public void should_return_false_while_if_forceClean_notRequested_and_indexExist() throws IOException { + String kind = "tenant1:avocet:completion:1.0.0"; + when(this.elasticIndexNameResolver.getIndexNameFromKind(kind)).thenReturn(kind.replace(":", "-")); + when(this.indicesService.isIndexExist(any(), any())).thenReturn(true); + + assertFalse(this.sut.isStorageSchemaSyncRequired(kind, false)); + } +} diff --git a/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/ReindexServiceTest.java b/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/ReindexServiceTest.java index e6a102b7821fa8a58bf38b0a992ce2203d8a2a47..1db13bb3827d6b691075b4ab089b5457e53b6b37 100644 --- a/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/ReindexServiceTest.java +++ b/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/ReindexServiceTest.java @@ -1,145 +1,154 @@ -// 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.indexer.ibm.service; - - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; -import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.indexer.service.ReindexServiceImpl; -import org.opengroup.osdu.indexer.service.StorageService; -import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; -import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.*; - -import static org.junit.Assert.fail; -import static org.mockito.MockitoAnnotations.initMocks; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.when; - -@Ignore -@RunWith(SpringRunner.class) -public class ReindexServiceTest { - - private final String cursor = "100"; - - private final String correlationId = UUID.randomUUID().toString(); - - @Mock - private StorageService storageService; - - @Mock - private Map<String, String> httpHeaders; - @Mock - private IRequestInfo requestInfo; - @Mock - private IndexerQueueTaskBuilder indexerQueueTaskBuilder; - @Mock - private JaxRsDpsLog log; - @InjectMocks - private ReindexServiceImpl sut; - - private RecordReindexRequest recordReindexRequest; - private RecordQueryResponse recordQueryResponse; - - @Before - public void setup() { - initMocks(this); - - mockStatic(UUID.class); - - recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor(cursor).build(); - recordQueryResponse = new RecordQueryResponse(); - - httpHeaders = new HashMap<>(); - httpHeaders.put(DpsHeaders.AUTHORIZATION, "testAuth"); - httpHeaders.put(DpsHeaders.CORRELATION_ID, correlationId); - DpsHeaders standardHeaders = DpsHeaders.createFromMap(httpHeaders); - when(requestInfo.getHeaders()).thenReturn(standardHeaders); - when(requestInfo.getHeadersMapWithDwdAuthZ()).thenReturn(httpHeaders); - } - - @Test - public void should_returnNull_givenNullResponseResult_reIndexRecordsTest() { - try { - recordQueryResponse.setResults(null); - when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - - String response = sut.reindexRecords(recordReindexRequest); - - Assert.assertNull(response); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test - public void should_returnNull_givenEmptyResponseResult_reIndexRecordsTest() { - try { - recordQueryResponse.setResults(new ArrayList<>()); - when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - - String response = sut.reindexRecords(recordReindexRequest); - - Assert.assertNull(response); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } - - @Test - public void should_returnRecordQueryRequestPayload_givenValidResponseResult_reIndexRecordsTest() { - try { - recordQueryResponse.setCursor(cursor); - List<String> results = new ArrayList<>(); - results.add("test1"); - recordQueryResponse.setResults(results); - when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - - String taskQueuePayload = sut.reindexRecords(recordReindexRequest); - - Assert.assertEquals("{\"kind\":\"tenant:test:test:1.0.0\",\"cursor\":\"100\"}", taskQueuePayload); - } catch (Exception e) { - fail("Should not throw exception" + e.getMessage()); - } - } - - @Test - public void should_returnRecordChangedMessage_givenValidResponseResult_reIndexRecordsTest() { - try { - List<String> results = new ArrayList<>(); - results.add("test1"); - recordQueryResponse.setResults(results); - when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); - - String taskQueuePayload = sut.reindexRecords(recordReindexRequest); - - Assert.assertEquals(String.format("{\"data\":\"[{\\\"id\\\":\\\"test1\\\",\\\"kind\\\":\\\"tenant:test:test:1.0.0\\\",\\\"op\\\":\\\"create\\\"}]\",\"attributes\":{\"slb-correlation-id\":\"%s\"}}", correlationId), taskQueuePayload); - } catch (Exception e) { - fail("Should not throw exception" + e.getMessage()); - } - } -} +// 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.indexer.ibm.service; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; +import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.search.Config; +import org.opengroup.osdu.indexer.service.ReindexServiceImpl; +import org.opengroup.osdu.indexer.service.StorageService; +import org.opengroup.osdu.indexer.util.IndexerQueueTaskBuilder; +import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.*; + +import static org.junit.Assert.fail; +import static org.mockito.MockitoAnnotations.initMocks; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +@Ignore +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(SpringRunner.class) +@PrepareForTest({Config.class}) +public class ReindexServiceTest { + + private final String cursor = "100"; + + private final String correlationId = UUID.randomUUID().toString(); + + @Mock + private StorageService storageService; + + @Mock + private Map<String, String> httpHeaders; + @Mock + private IRequestInfo requestInfo; + @Mock + private IndexerQueueTaskBuilder indexerQueueTaskBuilder; + @Mock + private JaxRsDpsLog log; + @InjectMocks + private ReindexServiceImpl sut; + + private RecordReindexRequest recordReindexRequest; + private RecordQueryResponse recordQueryResponse; + + @Before + public void setup() { + initMocks(this); + + mockStatic(UUID.class); + + recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor(cursor).build(); + recordQueryResponse = new RecordQueryResponse(); + + httpHeaders = new HashMap<>(); + httpHeaders.put(DpsHeaders.AUTHORIZATION, "testAuth"); + httpHeaders.put(DpsHeaders.CORRELATION_ID, correlationId); + DpsHeaders standardHeaders = DpsHeaders.createFromMap(httpHeaders); + when(requestInfo.getHeaders()).thenReturn(standardHeaders); + when(requestInfo.getHeadersMapWithDwdAuthZ()).thenReturn(httpHeaders); + } + + @Test + public void should_returnNull_givenNullResponseResult_reIndexRecordsTest() { + try { + recordQueryResponse.setResults(null); + when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); + + String response = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertNull(response); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test + public void should_returnNull_givenEmptyResponseResult_reIndexRecordsTest() { + try { + recordQueryResponse.setResults(new ArrayList<>()); + when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); + + String response = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertNull(response); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test + public void should_returnRecordQueryRequestPayload_givenValidResponseResult_reIndexRecordsTest() { + try { + recordQueryResponse.setCursor(cursor); + List<String> results = new ArrayList<>(); + results.add("test1"); + recordQueryResponse.setResults(results); + + mockStatic(Config.class); + when(Config.getStorageRecordsBatchSize()).thenReturn(1); + + when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); + + String taskQueuePayload = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertEquals("{\"kind\":\"tenant:test:test:1.0.0\",\"cursor\":\"100\"}", taskQueuePayload); + } catch (Exception e) { + fail("Should not throw exception" + e.getMessage()); + } + } + + @Test + public void should_returnRecordChangedMessage_givenValidResponseResult_reIndexRecordsTest() { + try { + List<String> results = new ArrayList<>(); + results.add("test1"); + recordQueryResponse.setResults(results); + when(storageService.getRecordsByKind(ArgumentMatchers.any())).thenReturn(recordQueryResponse); + + String taskQueuePayload = sut.reindexRecords(recordReindexRequest, false); + + Assert.assertEquals(String.format("{\"data\":\"[{\\\"id\\\":\\\"test1\\\",\\\"kind\\\":\\\"tenant:test:test:1.0.0\\\",\\\"op\\\":\\\"create\\\"}]\",\"attributes\":{\"slb-correlation-id\":\"%s\"}}", correlationId), taskQueuePayload); + } catch (Exception e) { + fail("Should not throw exception" + e.getMessage()); + } + } +} diff --git a/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/StorageServiceTest.java b/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/StorageServiceTest.java index 93e90b30e58f53034af05e8580b945c759f85fe9..013a5a7210806127947185924268cda94e7166cc 100644 --- a/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/StorageServiceTest.java +++ b/provider/indexer-ibm/src/test/java/org/opengroup/osdu/indexer/ibm/service/StorageServiceTest.java @@ -1,211 +1,230 @@ -// 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.indexer.ibm.service; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.opengroup.osdu.core.common.model.indexer.RecordInfo; -import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; -import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; -import org.opengroup.osdu.core.common.model.indexer.Records; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.indexer.service.StorageServiceImpl; -import org.opengroup.osdu.core.common.model.indexer.JobStatus; -import org.opengroup.osdu.core.common.model.http.HttpResponse; -import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; -import org.opengroup.osdu.core.common.http.IUrlFetchService; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.springframework.http.HttpStatus; -import org.springframework.test.context.junit4.SpringRunner; - -import java.lang.reflect.Type; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import static org.junit.Assert.*; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.powermock.api.mockito.PowerMockito.when; - -@Ignore -@RunWith(SpringRunner.class) -public class StorageServiceTest { - - @Mock - private IUrlFetchService urlFetchService; - @Mock - private JobStatus jobStatus; - @Mock - private JaxRsDpsLog log; - @Mock - private IRequestInfo requestInfo; - @InjectMocks - private StorageServiceImpl sut; - - private List<String> ids; - - @Before - public void setup() { - - String recordChangedMessages = "[{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"purge\"}," + - "{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"create\"}]"; - - when(this.requestInfo.getHeadersMap()).thenReturn(new HashMap<>()); - - Type listType = new TypeToken<List<RecordInfo>>() {}.getType(); - - List<RecordInfo> msgs = (new Gson()).fromJson(recordChangedMessages, listType); - jobStatus.initialize(msgs); - ids = Arrays.asList("tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465", "tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465"); - } - - @Test - public void should_return404_givenNullData_getValidStorageRecordsTest() throws URISyntaxException { - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(null); - - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); - - should_return404_getValidStorageRecordsTest(); - } - - @Test - public void should_return404_givenEmptyData_getValidStorageRecordsTest() throws URISyntaxException { - - String emptyDataFromStorage = "{\"records\":[],\"notFound\":[]}"; - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(emptyDataFromStorage); - - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); - - should_return404_getValidStorageRecordsTest(); - } - - @Test - public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsTest() throws URISyntaxException { - - String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"]}"; - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); - - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); - Records storageRecords = this.sut.getStorageRecords(ids); - - assertEquals(1, storageRecords.getRecords().size()); - } - - @Test - public void should_returnValidResponse_givenValidRecordQueryRequest_getRecordListByKind() throws Exception { - - RecordReindexRequest recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor("100").build(); - - HttpResponse httpResponse = new HttpResponse(); - httpResponse.setBody(new Gson().toJson(recordReindexRequest, RecordReindexRequest.class)); - - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); - - RecordQueryResponse recordQueryResponse = this.sut.getRecordsByKind(recordReindexRequest); - - assertEquals("100", recordQueryResponse.getCursor()); - assertNull(recordQueryResponse.getResults()); - } - - @Test - public void should_returnValidResponse_givenValidKind_getSchemaByKind() throws Exception { - - String validSchemaFromStorage = "{" + - " \"kind\": \"tenant:test:test:1.0.0\"," + - " \"schema\": [" + - " {" + - " \"path\": \"msg\"," + - " \"kind\": \"string\"" + - " }," + - " {" + - " \"path\": \"references.entity\"," + - " \"kind\": \"string\"" + - " }" + - " ]," + - " \"ext\": null" + - "}"; - String kind = "tenant:test:test:1.0.0"; - - HttpResponse httpResponse = new HttpResponse(); - httpResponse.setResponseCode(HttpStatus.OK.value()); - httpResponse.setBody(validSchemaFromStorage); - - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); - - String recordSchemaResponse = this.sut.getStorageSchema(kind); - - assertNotNull(recordSchemaResponse); - } - - @Test - public void should_returnNullResponse_givenAbsentKind_getSchemaByKind() throws Exception { - - String kind = "tenant:test:test:1.0.0"; - - HttpResponse httpResponse = new HttpResponse(); - httpResponse.setResponseCode(HttpStatus.NOT_FOUND.value()); - - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); - - String recordSchemaResponse = this.sut.getStorageSchema(kind); - - assertNull(recordSchemaResponse); - } - - @Test - public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsWithInvalidConversionTest() throws URISyntaxException { - - String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"],\"conversionStatuses\": [{\"id\":\"testid\",\"status\":\"ERROR\",\"errors\":[\"conversion error occured\"] } ]}"; - - HttpResponse httpResponse = mock(HttpResponse.class); - Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); - - when(this.urlFetchService.sendRequest(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(httpResponse); - Records storageRecords = this.sut.getStorageRecords(ids); - - assertEquals(1, storageRecords.getRecords().size()); - - assertEquals(1, storageRecords.getConversionStatuses().get(0).getErrors().size()); - - assertEquals("conversion error occured", storageRecords.getConversionStatuses().get(0).getErrors().get(0)); - } - - private void should_return404_getValidStorageRecordsTest() { - try { - this.sut.getStorageRecords(ids); - fail("Should throw exception"); - } catch (AppException e) { - assertEquals(HttpStatus.NOT_FOUND, e.getError().getCode()); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } - } -} +// 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.indexer.ibm.service; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.opengroup.osdu.core.common.model.indexer.IndexingStatus; +import org.opengroup.osdu.core.common.model.indexer.RecordInfo; +import org.opengroup.osdu.core.common.model.indexer.RecordQueryResponse; +import org.opengroup.osdu.core.common.model.indexer.RecordReindexRequest; +import org.opengroup.osdu.core.common.model.indexer.Records; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.indexer.service.StorageServiceImpl; +import org.opengroup.osdu.core.common.model.indexer.JobStatus; +import org.opengroup.osdu.core.common.model.http.HttpResponse; +import org.opengroup.osdu.core.common.provider.interfaces.IRequestInfo; +import org.opengroup.osdu.core.common.http.IUrlFetchService; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit4.SpringRunner; + +import java.lang.reflect.Type; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.when; + +@Ignore +@RunWith(SpringRunner.class) +public class StorageServiceTest { + + @Mock + private IUrlFetchService urlFetchService; + @Mock + private JobStatus jobStatus; + @Mock + private JaxRsDpsLog log; + @Mock + private IRequestInfo requestInfo; + @InjectMocks + private StorageServiceImpl sut; + + private List<String> ids; + private static final String RECORD_ID1 = "tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465"; + private static final String RECORDS_ID2 = "tenant1:doc:15e790a69beb4d789b1f979e2af2e813"; + + @Before + public void setup() { + + String recordChangedMessages = "[{\"id\":\"tenant1:doc:1dbf528e0e0549cab7a08f29fbfc8465\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"purge\"}," + + "{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\",\"kind\":\"tenant1:testindexer1528919679710:well:1.0.0\",\"op\":\"create\"}]"; + + when(this.requestInfo.getHeadersMap()).thenReturn(new HashMap<>()); + + Type listType = new TypeToken<List<RecordInfo>>() {}.getType(); + + List<RecordInfo> msgs = (new Gson()).fromJson(recordChangedMessages, listType); + jobStatus.initialize(msgs); + ids = Arrays.asList(RECORD_ID1, RECORDS_ID2); + } + + @Test + public void should_return404_givenNullData_getValidStorageRecordsTest() throws URISyntaxException { + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(null); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + should_return404_getValidStorageRecordsTest(); + } + + @Test + public void should_return404_givenEmptyData_getValidStorageRecordsTest() throws URISyntaxException { + + String emptyDataFromStorage = "{\"records\":[],\"notFound\":[]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(emptyDataFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + should_return404_getValidStorageRecordsTest(); + } + + @Test + public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsTest() throws URISyntaxException { + + String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(ids); + + assertEquals(1, storageRecords.getRecords().size()); + } + + @Test + public void should_returnValidResponse_givenValidRecordQueryRequest_getRecordListByKind() throws Exception { + + RecordReindexRequest recordReindexRequest = RecordReindexRequest.builder().kind("tenant:test:test:1.0.0").cursor("100").build(); + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setBody(new Gson().toJson(recordReindexRequest, RecordReindexRequest.class)); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + RecordQueryResponse recordQueryResponse = this.sut.getRecordsByKind(recordReindexRequest); + + assertEquals("100", recordQueryResponse.getCursor()); + assertNull(recordQueryResponse.getResults()); + } + + @Test + public void should_returnValidJobStatus_givenFailedUnitsConversion_processRecordChangedMessageTest() throws URISyntaxException { + String validDataFromStorage = "{\"records\":[{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[],\"conversionStatuses\":[{\"id\":\"tenant1:doc:15e790a69beb4d789b1f979e2af2e813\",\"status\":\"ERROR\",\"errors\":[\"crs conversion failed\"]}]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(singletonList("tenant1:doc:15e790a69beb4d789b1f979e2af2e813")); + + assertEquals(1, storageRecords.getRecords().size()); + verify(this.jobStatus).addOrUpdateRecordStatus(RECORDS_ID2, IndexingStatus.WARN, HttpStatus.BAD_REQUEST.value(), "crs conversion failed", String.format("record-id: %s | %s", "tenant1:doc:15e790a69beb4d789b1f979e2af2e813", "crs conversion failed")); + } + + @Test + public void should_returnValidResponse_givenValidKind_getSchemaByKind() throws Exception { + + String validSchemaFromStorage = "{" + + " \"kind\": \"tenant:test:test:1.0.0\"," + + " \"schema\": [" + + " {" + + " \"path\": \"msg\"," + + " \"kind\": \"string\"" + + " }," + + " {" + + " \"path\": \"references.entity\"," + + " \"kind\": \"string\"" + + " }" + + " ]," + + " \"ext\": null" + + "}"; + String kind = "tenant:test:test:1.0.0"; + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setResponseCode(HttpStatus.OK.value()); + httpResponse.setBody(validSchemaFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + String recordSchemaResponse = this.sut.getStorageSchema(kind); + + assertNotNull(recordSchemaResponse); + } + + @Test + public void should_returnNullResponse_givenAbsentKind_getSchemaByKind() throws Exception { + + String kind = "tenant:test:test:1.0.0"; + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setResponseCode(HttpStatus.NOT_FOUND.value()); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + + String recordSchemaResponse = this.sut.getStorageSchema(kind); + + assertNull(recordSchemaResponse); + } + + @Test + public void should_returnOneValidRecords_givenValidData_getValidStorageRecordsWithInvalidConversionTest() throws URISyntaxException { + + String validDataFromStorage = "{\"records\":[{\"id\":\"testid\", \"version\":1, \"kind\":\"tenant:test:test:1.0.0\"}],\"notFound\":[\"invalid1\"],\"conversionStatuses\": [{\"id\":\"testid\",\"status\":\"ERROR\",\"errors\":[\"conversion error occurred\"] } ]}"; + + HttpResponse httpResponse = mock(HttpResponse.class); + Mockito.when(httpResponse.getBody()).thenReturn(validDataFromStorage); + + when(this.urlFetchService.sendRequest(ArgumentMatchers.any())).thenReturn(httpResponse); + Records storageRecords = this.sut.getStorageRecords(ids); + + assertEquals(1, storageRecords.getRecords().size()); + + assertEquals(1, storageRecords.getConversionStatuses().get(0).getErrors().size()); + + assertEquals("conversion error occurred", storageRecords.getConversionStatuses().get(0).getErrors().get(0)); + } + + private void should_return404_getValidStorageRecordsTest() { + try { + this.sut.getStorageRecords(ids); + fail("Should throw exception"); + } catch (AppException e) { + assertEquals(HttpStatus.NOT_FOUND, e.getError().getCode()); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } +} diff --git a/testing/indexer-test-azure/pom.xml b/testing/indexer-test-azure/pom.xml index 0dc9d6c176adb45c71059fc4df35c7a60fe04f30..7d96ec79c242458691258ce53923882c3556a2f6 100644 --- a/testing/indexer-test-azure/pom.xml +++ b/testing/indexer-test-azure/pom.xml @@ -100,7 +100,7 @@ <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> - <version>2.23.0</version> + <version>2.26.0</version> <scope>test</scope> </dependency> diff --git a/testing/indexer-test-core/pom.xml b/testing/indexer-test-core/pom.xml index 74ab140f67da111aaa517cc068567979a593b034..eb98c8fcb5ef9c6b195e4c069a561cd7923f02c0 100644 --- a/testing/indexer-test-core/pom.xml +++ b/testing/indexer-test-core/pom.xml @@ -12,6 +12,7 @@ <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <cucumber.version>1.2.5</cucumber.version> + <os-core-common.version>0.0.18</os-core-common.version> </properties> <!-- testing core depends on core libraries in OSDU, so the repository needs to be configured --> @@ -21,22 +22,11 @@ <url>https://community.opengroup.org/api/v4/groups/17/-/packages/maven</url> </repository> </repositories> - - <distributionManagement> - <repository> - <id>${gitlab-server}</id> - <url>https://community.opengroup.org/api/v4/projects/25/packages/maven</url> - </repository> - <snapshotRepository> - <id>${gitlab-server}</id> - <url>https://community.opengroup.org/api/v4/projects/25/packages/maven</url> - </snapshotRepository> - </distributionManagement> <dependencies> <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>os-core-common</artifactId> - <version>0.0.13</version> + <version>${os-core-common.version}</version> </dependency> <dependency> diff --git a/testing/indexer-test-core/src/main/resources/features/indexrecord/IndexRecord.feature b/testing/indexer-test-core/src/main/resources/features/indexrecord/IndexRecord.feature index 3d7ccae8b7f836504007de9229096fa91583270d..30ee32eaa576dc0a7369c0a29a9110a20c566775 100644 --- a/testing/indexer-test-core/src/main/resources/features/indexrecord/IndexRecord.feature +++ b/testing/indexer-test-core/src/main/resources/features/indexrecord/IndexRecord.feature @@ -15,7 +15,8 @@ Feature: Indexing of the documents Examples: | kind | recordFile | number | index | type | acl | mapping | - | "tenant1:testindex<timestamp>:well:1.0.0" | "index_records_1" | 5 | "tenant1-testindex<timestamp>-well-1.0.0" | "well" | "data.default.viewers@opendes" | "{"mappings":{"well":{"dynamic":"false","properties":{"acl":{"properties":{"owners":{"type":"keyword"},"viewers":{"type":"keyword"}}},"ancestry":{"properties":{"parents":{"type":"keyword"}}},"data":{"properties":{"Basin":{"type":"text"},"Country":{"type":"text"},"County":{"type":"text"},"EmptyAttribute":{"type":"text"},"Established":{"type":"date"},"Field":{"type":"text"},"Location":{"type":"geo_point"},"OriginalOperator":{"type":"text"},"Rank":{"type":"integer"},"Score":{"type":"integer"},"State":{"type":"text"},"WellName":{"type":"text"},"WellStatus":{"type":"text"},"WellType":{"type":"text"}}},"id":{"type":"keyword"},"index":{"properties":{"lastUpdateTime":{"type":"date"},"statusCode":{"type":"integer"},"trace":{"type":"text"}}},"kind":{"type":"keyword"},"legal":{"properties":{"legaltags":{"type":"keyword"},"otherRelevantDataCountries":{"type":"keyword"},"status":{"type":"keyword"}}},"namespace":{"type":"keyword"},"type":{"type":"keyword"},"version":{"type":"long"},"x-acl":{"type":"keyword"}}}}}" | + | "tenant1:testindex<timestamp>:well:1.0.0" | "index_records_1" | 5 | "tenant1-testindex<timestamp>-well-1.0.0" | "well" | "data.default.viewers@opendes" | "{"mappings":{"well":{"dynamic":"false","properties":{"acl":{"properties":{"owners":{"type":"keyword"},"viewers":{"type":"keyword"}}},"ancestry":{"properties":{"parents":{"type":"keyword"}}},"data":{"properties":{"Basin":{"type":"text"},"Country":{"type":"text"},"County":{"type":"text"},"EmptyAttribute":{"type":"text"},"Established":{"type":"date"},"Field":{"type":"text"},"Location":{"type":"geo_point"},"OriginalOperator":{"type":"text"},"Rank":{"type":"integer"},"Score":{"type":"integer"},"State":{"type":"text"},"WellName":{"type":"text"},"WellStatus":{"type":"text"},"WellType":{"type":"text"},"DblArray":{"type":"double"}}},"id":{"type":"keyword"},"index":{"properties":{"lastUpdateTime":{"type":"date"},"statusCode":{"type":"integer"},"trace":{"type":"text"}}},"kind":{"type":"keyword"},"legal":{"properties":{"legaltags":{"type":"keyword"},"otherRelevantDataCountries":{"type":"keyword"},"status":{"type":"keyword"}}},"namespace":{"type":"keyword"},"type":{"type":"keyword"},"version":{"type":"long"},"x-acl":{"type":"keyword"}}}}}" | + | "tenant1:testindex<timestamp>:well:3.0.0" | "index_records_1" | 5 | "tenant1-testindex<timestamp>-well-3.0.0" | "well" | "data.default.viewers@opendes" | "{"mappings":{"well":{"dynamic":"false","properties":{"acl":{"properties":{"owners":{"type":"keyword"},"viewers":{"type":"keyword"}}},"ancestry":{"properties":{"parents":{"type":"keyword"}}},"data":{"properties":{"Basin":{"type":"text"},"Country":{"type":"text"},"County":{"type":"text"},"EmptyAttribute":{"type":"text"},"Established":{"type":"date"},"Field":{"type":"text"},"Location":{"type":"geo_point"},"OriginalOperator":{"type":"text"},"Rank":{"type":"integer"},"Score":{"type":"integer"},"State":{"type":"text"},"WellName":{"type":"text"},"WellStatus":{"type":"text"},"WellType":{"type":"text"},"DblArray":{"type":"double"}}},"id":{"type":"keyword"},"index":{"properties":{"lastUpdateTime":{"type":"date"},"statusCode":{"type":"integer"},"trace":{"type":"text"}}},"kind":{"type":"keyword"},"legal":{"properties":{"legaltags":{"type":"keyword"},"otherRelevantDataCountries":{"type":"keyword"},"status":{"type":"keyword"}}},"namespace":{"type":"keyword"},"type":{"type":"keyword"},"version":{"type":"long"},"x-acl":{"type":"keyword"}}}}}" | Scenario Outline: Ingest the record and Index in the Elastic Search with bad attribute When I ingest records with the <recordFile> with <acl> for a given <kind> diff --git a/testing/indexer-test-core/src/main/resources/testData/index_records_1.json b/testing/indexer-test-core/src/main/resources/testData/index_records_1.json index 145b5a78bab6867d7635f360383fbedac272a64c..0d9f3c17ef24167affb538fc7db995317a77c348 100644 --- a/testing/indexer-test-core/src/main/resources/testData/index_records_1.json +++ b/testing/indexer-test-core/src/main/resources/testData/index_records_1.json @@ -1,112 +1,114 @@ -[ - { - "id": "tenant1:ihs:testIngest2<timestamp>", - "data": { - "Field": "OSDU OFFICE - 2", - "Location": { - "latitude":32.406402588, - "longitude":-86.565592762 - }, - "Basin": "Houston", - "County": "Harris", - "State": "TX", - "Country": "USA", - "WellStatus": "Under development", - "OriginalOperator": "OFFICE - 2", - "WellName": "Data Platform Services", - "WellType": "Data Lake Cloud", - "EmptyAttribute": "", - "Rank": 1, - "Score" : 10, - "Established": "2000-03-27T23:38:48Z" - } - }, - { - "id": "tenant1:ihs:testIngest3<timestamp>", - "data": { - "Field": "OSDU OFFICE - 2", - "Location": { - "latitude":32.406402588, - "longitude":-86.565592762 - }, - "Basin": "Houston", - "County": "Harris", - "State": "TX", - "Country": "USA", - "WellStatus": "Under development", - "OriginalOperator": "OFFICE2", - "WellName": "Data Platform Services", - "WellType": "Data Lake Cloud", - "EmptyAttribute": "", - "Rank": 1, - "Score" : 10, - "Established": "2000-03-27T23:38:48Z" - } - }, - { - "id": "tenant1:ihs:testIngest4<timestamp>", - "data": { - "Field": "OSDU OFFICE - 2", - "Location": { - "latitude":32.406402588, - "longitude":-86.565592762 - }, - "Basin": "Houston", - "County": "Harris", - "State": "TX", - "Country": "USA", - "WellStatus": "Under development", - "OriginalOperator": "OFFICE2", - "WellName": "Data Platform Services", - "WellType": "Data Lake Cloud", - "EmptyAttribute": "", - "Rank": 1, - "Score" : 10, - "Established": "2000-03-27T23:38:48Z" - } - }, - { - "id": "tenant1:ihs:testIngest5<timestamp>", - "data": { - "Field": "OSDU OFFICE - 2", - "Location": { - "latitude":32.406402588, - "longitude":-86.565592762 - }, - "Basin": "Houston", - "County": "Harris", - "State": "TX", - "Country": "USA", - "WellStatus": "Under development", - "OriginalOperator": "OFFICE2", - "WellName": "Data Platform Services", - "WellType": "Data Lake Cloud", - "EmptyAttribute": "", - "Rank": 1, - "Score" : 10, - "Established": "2000-03-27T23:38:48Z" - } - }, - { - "id": "tenant1:ihs:testIngest6<timestamp>", - "data": { - "Field": "OSDU OFFICE - 2", - "Location": { - "latitude":32.406402588, - "longitude":-86.565592762 - }, - "Basin": "Houston", - "County": "Harris", - "State": "TX", - "Country": "USA", - "WellStatus": "Under development", - "OriginalOperator": "OFFICE2", - "WellName": "Data Platform Services", - "WellType": "Data Lake Cloud", - "EmptyAttribute": "", - "Rank": 1, - "Score" : 10, - "Established": "2000-03-27T23:38:48Z" - } - } +[ + { + "id": "tenant1:ihs:testIngest2<timestamp>", + "data": { + "Field": "OSDU OFFICE - 2", + "Location": { + "latitude":32.406402588, + "longitude":-86.565592762 + }, + "Basin": "Houston", + "County": "Harris", + "State": "TX", + "Country": "USA", + "WellStatus": "Under development", + "OriginalOperator": "OFFICE - 2", + "WellName": "Data Platform Services", + "WellType": "Data Lake Cloud", + "EmptyAttribute": "", + "Rank": 1, + "Score" : 10, + "Established": "2000-03-27T23:38:48Z", + "DblArray": [32.40, 36.45, 40.0] + } + }, + { + "id": "tenant1:ihs:testIngest3<timestamp>", + "data": { + "Field": "OSDU OFFICE - 2", + "Location": { + "latitude":32.406402588, + "longitude":-86.565592762 + }, + "Basin": "Houston", + "County": "Harris", + "State": "TX", + "Country": "USA", + "WellStatus": "Under development", + "OriginalOperator": "OFFICE2", + "WellName": "Data Platform Services", + "WellType": "Data Lake Cloud", + "EmptyAttribute": "", + "Rank": 1, + "Score" : 10, + "Established": "2000-03-27T23:38:48Z", + "DblArray": [62.40, 60.0] + } + }, + { + "id": "tenant1:ihs:testIngest4<timestamp>", + "data": { + "Field": "OSDU OFFICE - 2", + "Location": { + "latitude":32.406402588, + "longitude":-86.565592762 + }, + "Basin": "Houston", + "County": "Harris", + "State": "TX", + "Country": "USA", + "WellStatus": "Under development", + "OriginalOperator": "OFFICE2", + "WellName": "Data Platform Services", + "WellType": "Data Lake Cloud", + "EmptyAttribute": "", + "Rank": 1, + "Score" : 10, + "Established": "2000-03-27T23:38:48Z" + } + }, + { + "id": "tenant1:ihs:testIngest5<timestamp>", + "data": { + "Field": "OSDU OFFICE - 2", + "Location": { + "latitude":32.406402588, + "longitude":-86.565592762 + }, + "Basin": "Houston", + "County": "Harris", + "State": "TX", + "Country": "USA", + "WellStatus": "Under development", + "OriginalOperator": "OFFICE2", + "WellName": "Data Platform Services", + "WellType": "Data Lake Cloud", + "EmptyAttribute": "", + "Rank": 1, + "Score" : 10, + "Established": "2000-03-27T23:38:48Z" + } + }, + { + "id": "tenant1:ihs:testIngest6<timestamp>", + "data": { + "Field": "OSDU OFFICE - 2", + "Location": { + "latitude":32.406402588, + "longitude":-86.565592762 + }, + "Basin": "Houston", + "County": "Harris", + "State": "TX", + "Country": "USA", + "WellStatus": "Under development", + "OriginalOperator": "OFFICE2", + "WellName": "Data Platform Services", + "WellType": "Data Lake Cloud", + "EmptyAttribute": "", + "Rank": 1, + "Score" : 10, + "Established": "2000-03-27T23:38:48Z" + } + } ] \ No newline at end of file diff --git a/testing/indexer-test-core/src/main/resources/testData/index_records_1.schema b/testing/indexer-test-core/src/main/resources/testData/index_records_1.schema index bfdc78abd4664a98824562eab09d32ffc283395d..a67be0f5383d708571bcbc03b69cb7c818d860d4 100644 --- a/testing/indexer-test-core/src/main/resources/testData/index_records_1.schema +++ b/testing/indexer-test-core/src/main/resources/testData/index_records_1.schema @@ -56,6 +56,10 @@ { "path": "Established", "kind": "datetime" + }, + { + "path": "DblArray", + "kind": "[]double" } ] } \ No newline at end of file