diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt b/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt index e8a9b14fc..62a88a585 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt @@ -16,6 +16,7 @@ import org.opensearch.alerting.action.GetAlertsAction import org.opensearch.alerting.action.GetDestinationsAction import org.opensearch.alerting.action.GetEmailAccountAction import org.opensearch.alerting.action.GetEmailGroupAction +import org.opensearch.alerting.action.GetFindingsSearchAction import org.opensearch.alerting.action.GetMonitorAction import org.opensearch.alerting.action.IndexDestinationAction import org.opensearch.alerting.action.IndexEmailAccountAction @@ -51,6 +52,7 @@ import org.opensearch.alerting.resthandler.RestGetAlertsAction import org.opensearch.alerting.resthandler.RestGetDestinationsAction import org.opensearch.alerting.resthandler.RestGetEmailAccountAction import org.opensearch.alerting.resthandler.RestGetEmailGroupAction +import org.opensearch.alerting.resthandler.RestGetFindingsSearchAction import org.opensearch.alerting.resthandler.RestGetMonitorAction import org.opensearch.alerting.resthandler.RestIndexDestinationAction import org.opensearch.alerting.resthandler.RestIndexEmailAccountAction @@ -74,6 +76,7 @@ import org.opensearch.alerting.transport.TransportGetAlertsAction import org.opensearch.alerting.transport.TransportGetDestinationsAction import org.opensearch.alerting.transport.TransportGetEmailAccountAction import org.opensearch.alerting.transport.TransportGetEmailGroupAction +import org.opensearch.alerting.transport.TransportGetFindingsSearchAction import org.opensearch.alerting.transport.TransportGetMonitorAction import org.opensearch.alerting.transport.TransportIndexDestinationAction import org.opensearch.alerting.transport.TransportIndexEmailAccountAction @@ -139,6 +142,7 @@ internal class AlertingPlugin : PainlessExtension, ActionPlugin, ScriptPlugin, R @JvmField val EMAIL_GROUP_BASE_URI = "$DESTINATION_BASE_URI/email_groups" @JvmField val LEGACY_OPENDISTRO_EMAIL_ACCOUNT_BASE_URI = "$LEGACY_OPENDISTRO_DESTINATION_BASE_URI/email_accounts" @JvmField val LEGACY_OPENDISTRO_EMAIL_GROUP_BASE_URI = "$LEGACY_OPENDISTRO_DESTINATION_BASE_URI/email_groups" + @JvmField val FINDING_BASE_URI = "/_plugins/_alerting/findings" @JvmField val ALERTING_JOB_TYPES = listOf("monitor") } @@ -178,7 +182,8 @@ internal class AlertingPlugin : PainlessExtension, ActionPlugin, ScriptPlugin, R RestSearchEmailGroupAction(), RestGetEmailGroupAction(), RestGetDestinationsAction(), - RestGetAlertsAction() + RestGetAlertsAction(), + RestGetFindingsSearchAction() ) } @@ -202,7 +207,9 @@ internal class AlertingPlugin : PainlessExtension, ActionPlugin, ScriptPlugin, R ActionPlugin.ActionHandler(SearchEmailGroupAction.INSTANCE, TransportSearchEmailGroupAction::class.java), ActionPlugin.ActionHandler(DeleteEmailGroupAction.INSTANCE, TransportDeleteEmailGroupAction::class.java), ActionPlugin.ActionHandler(GetDestinationsAction.INSTANCE, TransportGetDestinationsAction::class.java), - ActionPlugin.ActionHandler(GetAlertsAction.INSTANCE, TransportGetAlertsAction::class.java) + ActionPlugin.ActionHandler(GetAlertsAction.INSTANCE, TransportGetAlertsAction::class.java), + ActionPlugin.ActionHandler(GetFindingsSearchAction.INSTANCE, TransportGetFindingsSearchAction::class.java) + ) } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchAction.kt new file mode 100644 index 000000000..67b9c7a1c --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchAction.kt @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.action + +import org.opensearch.action.ActionType + +class GetFindingsSearchAction private constructor() : ActionType(NAME, ::GetFindingsSearchResponse) { + companion object { + val INSTANCE = GetFindingsSearchAction() + val NAME = "cluster:admin/opendistro/alerting/findings/get" + } +} diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchRequest.kt b/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchRequest.kt new file mode 100644 index 000000000..7712cb3c2 --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchRequest.kt @@ -0,0 +1,56 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.alerting.model.Table +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.search.fetch.subphase.FetchSourceContext +import java.io.IOException + +class GetFindingsSearchRequest : ActionRequest { + val findingId: String? + val version: Long + val srcContext: FetchSourceContext? + val table: Table + + constructor( + findingId: String?, + version: Long, + srcContext: FetchSourceContext?, + table: Table + ) : super() { + this.findingId = findingId + this.version = version + this.srcContext = srcContext + this.table = table + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + findingId = sin.readOptionalString(), + version = sin.readLong(), + srcContext = if (sin.readBoolean()) { + FetchSourceContext(sin) + } else null, + table = Table.readFrom(sin) + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeOptionalString(findingId) + out.writeLong(version) + out.writeBoolean(srcContext != null) + srcContext?.writeTo(out) + table.writeTo(out) + } +} diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchResponse.kt b/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchResponse.kt new file mode 100644 index 000000000..b203fa36a --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/action/GetFindingsSearchResponse.kt @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.action + +import org.opensearch.action.ActionResponse +import org.opensearch.alerting.model.FindingWithDocs +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.rest.RestStatus +import java.io.IOException + +class GetFindingsSearchResponse : ActionResponse, ToXContentObject { + var status: RestStatus + var totalFindings: Int? + var findings: List + + constructor( + status: RestStatus, + totalFindings: Int?, + findings: List + ) : super() { + this.status = status + this.totalFindings = totalFindings + this.findings = findings + } + + @Throws(IOException::class) + constructor(sin: StreamInput) { + this.status = sin.readEnum(RestStatus::class.java) + val findings = mutableListOf() + this.totalFindings = sin.readOptionalInt() + var currentSize = sin.readInt() + for (i in 0 until currentSize) { + findings.add(FindingWithDocs.readFrom(sin)) + } + this.findings = findings + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeEnum(status) + out.writeOptionalInt(totalFindings) + out.writeInt(findings.size) + for (finding in findings) { + finding.writeTo(out) + } + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field("totalFindings", totalFindings) + .field("findings", findings) + + return builder.endObject() + } +} diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/model/Finding.kt b/alerting/src/main/kotlin/org/opensearch/alerting/model/Finding.kt index ccd8689a3..5e46ee351 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/model/Finding.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/model/Finding.kt @@ -32,6 +32,7 @@ class Finding( val triggerId: String?, val triggerName: String? ) : Writeable, ToXContent { + @Throws(IOException::class) constructor(sin: StreamInput) : this( id = sin.readString(), @@ -73,7 +74,7 @@ class Finding( .field(QUERY_ID_FIELD, queryId) .field(QUERY_TAGS_FIELD, queryTags.toTypedArray()) .field(SEVERITY_FIELD, severity) - .field(TIMESTAMP_FIELD, timestamp) + .field(TIMESTAMP_FIELD, timestamp.toEpochMilli()) .field(TRIGGER_ID_FIELD, triggerId) .field(TRIGGER_NAME_FIELD, triggerName) builder.endObject() @@ -141,7 +142,9 @@ class Finding( } } SEVERITY_FIELD -> severity = xcp.text() - TIMESTAMP_FIELD -> timestamp = requireNotNull(xcp.instant()) + TIMESTAMP_FIELD -> { + timestamp = requireNotNull(xcp.instant()) + } TRIGGER_ID_FIELD -> triggerId = xcp.text() TRIGGER_NAME_FIELD -> triggerName = xcp.text() } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/model/FindingDocument.kt b/alerting/src/main/kotlin/org/opensearch/alerting/model/FindingDocument.kt new file mode 100644 index 000000000..aa89d061e --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/model/FindingDocument.kt @@ -0,0 +1,88 @@ +package org.opensearch.alerting.model + +import org.apache.logging.log4j.LogManager +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException + +private val log = LogManager.getLogger(FindingDocument::class.java) + +class FindingDocument( + val index: String, + val id: String, + val found: Boolean, + val document: MutableMap +) : Writeable, ToXContent { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + index = sin.readString(), + id = sin.readString(), + found = sin.readBoolean(), + document = sin.readMap() + ) + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(INDEX_FIELD, index) + .field(FINDING_DOCUMENT_ID_FIELD, id) + .field(FOUND_FIELD, found) + + if (document.isEmpty()) builder.field(DOCUMENT_FIELD, document) + builder.endObject() + return builder + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(index) + out.writeString(id) + out.writeBoolean(found) + out.writeMap(document) + } + + companion object { + const val INDEX_FIELD = "index" + const val FINDING_DOCUMENT_ID_FIELD = "id" + const val FOUND_FIELD = "found" + const val DOCUMENT_FIELD = "document" + const val NO_ID = "" + const val NO_INDEX = "" + + @JvmStatic @JvmOverloads + @Throws(IOException::class) + fun parse(xcp: XContentParser, id: String = NO_ID, index: String = NO_INDEX): FindingDocument { + var found = false + var document: MutableMap = mutableMapOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + FOUND_FIELD -> found = xcp.booleanValue() + DOCUMENT_FIELD -> document = xcp.map() + } + } + + return FindingDocument( + index = index, + id = id, + found = found, + document = document + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): FindingDocument { + return FindingDocument(sin) + } + } +} diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/model/FindingWithDocs.kt b/alerting/src/main/kotlin/org/opensearch/alerting/model/FindingWithDocs.kt new file mode 100644 index 000000000..d84195517 --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/model/FindingWithDocs.kt @@ -0,0 +1,80 @@ +package org.opensearch.alerting.model + +import org.apache.logging.log4j.LogManager +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException + +private val log = LogManager.getLogger(Finding::class.java) + +class FindingWithDocs( + val finding: Finding, + val documents: List +) : Writeable, ToXContent { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + finding = Finding.readFrom(sin), + documents = sin.readList((FindingDocument)::readFrom) + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + finding.writeTo(out) + documents.forEach { + it.writeTo(out) + } + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder.startObject() + .field(FINDING_FIELD, finding) + .field(DOCUMENTS_FIELD, documents) + builder.endObject() + return builder + } + + companion object { + const val FINDING_FIELD = "finding" + const val DOCUMENTS_FIELD = "documents" + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): FindingWithDocs { + lateinit var finding: Finding + val documents: MutableList = mutableListOf() + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + FINDING_FIELD -> finding = Finding.parse(xcp) + DOCUMENTS_FIELD -> { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { + documents.add(FindingDocument.parse(xcp)) + } + } + } + } + + return FindingWithDocs( + finding = finding, + documents = documents + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): FindingWithDocs { + return FindingWithDocs(sin) + } + } +} diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/resthandler/RestGetFindingsSearchAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/resthandler/RestGetFindingsSearchAction.kt new file mode 100644 index 000000000..fdf214930 --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/resthandler/RestGetFindingsSearchAction.kt @@ -0,0 +1,81 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.resthandler + +import org.apache.logging.log4j.LogManager +import org.opensearch.alerting.AlertingPlugin +import org.opensearch.alerting.action.GetFindingsSearchAction +import org.opensearch.alerting.action.GetFindingsSearchRequest +import org.opensearch.alerting.model.Table +import org.opensearch.alerting.util.context +import org.opensearch.client.node.NodeClient +import org.opensearch.rest.BaseRestHandler +import org.opensearch.rest.BaseRestHandler.RestChannelConsumer +import org.opensearch.rest.RestHandler.Route +import org.opensearch.rest.RestRequest +import org.opensearch.rest.RestRequest.Method.GET +import org.opensearch.rest.RestRequest.Method.POST +import org.opensearch.rest.action.RestActions +import org.opensearch.rest.action.RestToXContentListener +import org.opensearch.search.fetch.subphase.FetchSourceContext + +/** + * This class consists of the REST handler to search findings . + */ +class RestGetFindingsSearchAction : BaseRestHandler() { + + private val log = LogManager.getLogger(RestGetFindingsSearchAction::class.java) + + override fun getName(): String { + return "get_findings_search_action" + } + + override fun routes(): List { + return listOf( + Route(POST, "${AlertingPlugin.FINDING_BASE_URI}/_search"), + Route(GET, "${AlertingPlugin.FINDING_BASE_URI}/_search") + ) + } + + override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + log.info("Entering RestGetFindingsSearchAction.kt.") + log.info("${request.method()} ${request.path()}") + + val findingID: String? = request.param("findingID") + + var srcContext = context(request) + if (request.method() == RestRequest.Method.HEAD) { + srcContext = FetchSourceContext.DO_NOT_FETCH_SOURCE + } + + val sortString = request.param("sortString", "id.keyword") + val sortOrder = request.param("sortOrder", "asc") + val missing: String? = request.param("missing") + val size = request.paramAsInt("size", 20) + val startIndex = request.paramAsInt("startIndex", 0) + val searchString = request.param("searchString", "") + + val table = Table( + sortOrder, + sortString, + missing, + size, + startIndex, + searchString + ) + + val getFindingsSearchRequest = GetFindingsSearchRequest( + findingID, + RestActions.parseVersion(request), + srcContext, + table + ) + return RestChannelConsumer { + channel -> + client.execute(GetFindingsSearchAction.INSTANCE, getFindingsSearchRequest, RestToXContentListener(channel)) + } + } +} diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetFindingsSearchAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetFindingsSearchAction.kt new file mode 100644 index 000000000..95b9e99ba --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetFindingsSearchAction.kt @@ -0,0 +1,186 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.transport + +import org.apache.logging.log4j.LogManager +import org.opensearch.action.ActionListener +import org.opensearch.action.get.MultiGetRequest +import org.opensearch.action.get.MultiGetResponse +import org.opensearch.action.search.SearchRequest +import org.opensearch.action.search.SearchResponse +import org.opensearch.action.support.ActionFilters +import org.opensearch.action.support.HandledTransportAction +import org.opensearch.alerting.action.GetFindingsSearchAction +import org.opensearch.alerting.action.GetFindingsSearchRequest +import org.opensearch.alerting.action.GetFindingsSearchResponse +import org.opensearch.alerting.elasticapi.addFilter +import org.opensearch.alerting.model.Finding +import org.opensearch.alerting.model.FindingWithDocs +import org.opensearch.alerting.settings.AlertingSettings +import org.opensearch.alerting.util.AlertingException +import org.opensearch.client.Client +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.Strings +import org.opensearch.common.inject.Inject +import org.opensearch.common.settings.Settings +import org.opensearch.common.xcontent.LoggingDeprecationHandler +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.common.xcontent.XContentType +import org.opensearch.commons.authuser.User +import org.opensearch.index.query.QueryBuilders +import org.opensearch.rest.RestStatus +import org.opensearch.search.builder.SearchSourceBuilder +import org.opensearch.search.fetch.subphase.FetchSourceContext +import org.opensearch.search.sort.SortBuilders +import org.opensearch.search.sort.SortOrder +import org.opensearch.tasks.Task +import org.opensearch.transport.TransportService +import java.io.IOException + +private val log = LogManager.getLogger(TransportGetFindingsSearchAction::class.java) + +class TransportGetFindingsSearchAction @Inject constructor( + transportService: TransportService, + val client: Client, + clusterService: ClusterService, + actionFilters: ActionFilters, + val settings: Settings, + val xContentRegistry: NamedXContentRegistry +) : HandledTransportAction ( + GetFindingsSearchAction.NAME, transportService, actionFilters, ::GetFindingsSearchRequest +), + SecureTransportAction { + + @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + + init { + listenFilterBySettingChange(clusterService) + } + + override fun doExecute( + task: Task, + getFindingsSearchRequest: GetFindingsSearchRequest, + actionListener: ActionListener + ) { + val user = readUserFromThreadContext(client) + val tableProp = getFindingsSearchRequest.table + + val sortBuilder = SortBuilders + .fieldSort(tableProp.sortString) + .order(SortOrder.fromString(tableProp.sortOrder)) + if (!tableProp.missing.isNullOrBlank()) { + sortBuilder.missing(tableProp.missing) + } + + val searchSourceBuilder = SearchSourceBuilder() + .sort(sortBuilder) + .size(tableProp.size) + .from(tableProp.startIndex) + .fetchSource(FetchSourceContext(true, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY)) + .seqNoAndPrimaryTerm(true) + .version(true) + val matchAllQueryBuilder = QueryBuilders.matchAllQuery() + // TODO: Update query to support other parameters of search + + searchSourceBuilder.query(matchAllQueryBuilder) + + client.threadPool().threadContext.stashContext().use { + resolve(searchSourceBuilder, actionListener, user) + } + } + + fun resolve( + searchSourceBuilder: SearchSourceBuilder, + actionListener: ActionListener, + user: User? + ) { + if (user == null) { + // user is null when: 1/ security is disabled. 2/when user is super-admin. + search(searchSourceBuilder, actionListener) + } else if (!doFilterForUser(user)) { + // security is enabled and filterby is disabled. + search(searchSourceBuilder, actionListener) + } else { + // security is enabled and filterby is enabled. + try { + log.info("Filtering result by: ${user.backendRoles}") + addFilter(user, searchSourceBuilder, "finding.user.backend_roles.keyword") + search(searchSourceBuilder, actionListener) + } catch (ex: IOException) { + actionListener.onFailure(AlertingException.wrap(ex)) + } + } + } + + fun search(searchSourceBuilder: SearchSourceBuilder, actionListener: ActionListener) { + val searchRequest = SearchRequest() + .source(searchSourceBuilder) + .indices(".opensearch-alerting-findings") + client.search( + searchRequest, + object : ActionListener { + override fun onResponse(response: SearchResponse) { + val totalFindingCount = response.hits.totalHits?.value?.toInt() + val mgetRequest = MultiGetRequest() + val findingsWithDocs = mutableListOf() + for (hit in response.hits) { + val id = hit.id + val xcp = XContentFactory.xContent(XContentType.JSON) + .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, hit.sourceAsString) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + val finding = Finding.parse(xcp, id) + val documentIds = finding.relatedDocId.split(",").toTypedArray() + // Add getRequests to mget request + documentIds.forEach { + // TODO: check if we want to add individual get document request, or use documentIds array for a single finding related_docs + docId -> + mgetRequest.add(MultiGetRequest.Item(finding.index, docId)) + } + } + searchDocument(mgetRequest, totalFindingCount, actionListener) + } + + override fun onFailure(t: Exception) { + actionListener.onFailure(AlertingException.wrap(t)) + } + } + ) + } + + fun searchDocument( + mgetRequest: MultiGetRequest, + totalFindingCount: Int?, + actionListener: ActionListener + ) { + client.multiGet( + mgetRequest, + object : ActionListener { + override fun onResponse(response: MultiGetResponse) { + val findingsWithDocs: List = mutableListOf() + response.responses.forEach { + // TODO: REMOVE DEBUG LOG + log.info("response: ${response.toString()}") + /* val xcp = XContentFactory.xContent(XContentType.JSON) + .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, response.toString()) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp) + val findingDocument = FindingDocument.parse(xcp) + */ + // TODO: Parse the searched documents and add to list of findingWithDocs, need to associate original finding id to response + } + // TODO: Form the response here with the map/list of findings + actionListener.onResponse(GetFindingsSearchResponse(RestStatus.OK, totalFindingCount, findingsWithDocs)) + } + + override fun onFailure(t: Exception) { + actionListener.onFailure(AlertingException.wrap(t)) + } + } + ) + } +}