…mapping traversal
Fix A (MonitorMetadataService.kt): guard lastRunContext cast in recreateRunContext() —
if lastRunContext.isEmpty() pass null to createFullRunContext() (which accepts null and
builds a fresh context) instead of casting EmptyMap to MutableMap, which throws
ClassCastException on monitors that have no last_run_context in stored metadata.
Fix B (DocLevelMonitorQueries.kt): replace bare MutableMap casts in
traverseMappingsAndUpdate() with .toMutableMap() defensive copies so that
Collections.emptyMap() values from index mapping nested objects {} do not cause
UnsupportedOperationException when the traversal tries to mutate them.
Fix C (MonitorFanOutUtils.kt / DocumentLevelMonitorRunner.kt /
RemoteDocumentLevelMonitorRunner.kt): tighten initializeNewLastRunContext() return
type from Map to MutableMap (matching the actual runtime type) and remove the now-
unnecessary as MutableMap casts at both call sites.
Related: opensearch-project/common-utils#967
Signed-off-by: thecodingshrimp <leonard.stutzer@sap.com>
Summary
This PR adds defence-in-depth guards against
ClassCastException/UnsupportedOperationExceptioncaused by immutable empty maps being cast to
MutableMapin the doc-level monitor subsystem.Closes: #2220
Related: opensearch-project/common-utils#967 (root-cause fix belongs there)
Root cause
MonitorMetadata.lastRunContextdefaults tokotlin.collections.EmptyMap(the immutablesingleton returned by
mapOf()) when deserialized from.opensearch-alerting-configandthe stored document has no
last_run_contextfield (monitor never ran, or created underan older plugin version).
MonitorMetadataService.recreateRunContext()line 210 casts this directly toMutableMap<String, MutableMap<String, Any>>.EmptyMapis not aMutableMap, so theJVM throws
ClassCastExceptionon anyPUTupdate to such a monitor.The correct fix — changing
mapOf()tomutableMapOf()inMonitorMetadata.kt— istracked in opensearch-project/common-utils#967. This PR adds a guard at the cast site so
that the crash cannot occur regardless of which
common-utilsversion is deployed.Changes
Fix A —
MonitorMetadataService.kt(critical)Guard the cast at line 210: if
lastRunContext.isEmpty(), passnulltocreateFullRunContext()(which acceptsnulland builds a fresh context).Fix B —
DocLevelMonitorQueries.kt(plausible crash path)traverseMappingsAndUpdate()casts values fromindexMetadata.mapping()?.sourceAsMapdirectly to
MutableMap. Java'sCollections.emptyMap()(returned for empty nestedobjects
{}) is not mutable;.put()/.remove()would throwUnsupportedOperationException. Replace bare casts with.toMutableMap()defensivecopies at the three cast sites inside
traverseMappingsAndUpdate().Fix C —
MonitorFanOutUtils.kt/DocumentLevelMonitorRunner.kt(contract)initializeNewLastRunContext()always returns aMutableMapbut its return type isdeclared as
Map. Update the declaration and remove the now-unnecessaryas MutableMapcast at the call site in
DocumentLevelMonitorRunner.ktandRemoteDocumentLevelMonitorRunner.kt.Files changed
alerting/src/main/kotlin/org/opensearch/alerting/MonitorMetadataService.ktalerting/src/main/kotlin/org/opensearch/alerting/util/DocLevelMonitorQueries.ktalerting/src/main/kotlin/org/opensearch/alerting/MonitorFanOutUtils.ktalerting/src/main/kotlin/org/opensearch/alerting/DocumentLevelMonitorRunner.ktalerting/src/main/kotlin/org/opensearch/alerting/remote/monitors/RemoteDocumentLevelMonitorRunner.ktTesting
./gradlew test).opensearch-alerting-config, thenPUTan update to the monitor — noClassCastExceptionthrown(
{}) does not throwUnsupportedOperationException