diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java index c96346dc88..2d913e0269 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java @@ -22,6 +22,12 @@ import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; +// These DB keys have been deprecated: +// https://github.com/open-telemetry/semantic-conventions-java/blob/release/v1.34.0/semconv-incubating/src/main/java/io/opentelemetry/semconv/incubating/DbIncubatingAttributes.java#L322-L327 +// They have been replaced with new keys: +// https://github.com/open-telemetry/semantic-conventions-java/blob/release/v1.34.0/semconv/src/main/java/io/opentelemetry/semconv/DbAttributes.java#L77 +// TODO: Supporting new keys. Cannot do this now as new keys are not available in OTel Agent 2.11. +// TODO: Delete deprecated keys once they no longer exist in binding version of the upstream code. import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; @@ -34,7 +40,10 @@ import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_METHOD; import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_STATUS_CODE; import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_URL; +// https://github.com/open-telemetry/semantic-conventions-java/blob/release/v1.34.0/semconv-incubating/src/main/java/io/opentelemetry/semconv/incubating/MessagingIncubatingAttributes.java#L236-L242 +// Deprecated, use {@code messaging.operation.type} instead. import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static io.opentelemetry.semconv.incubating.NetIncubatingAttributes.NET_PEER_NAME; import static io.opentelemetry.semconv.incubating.NetIncubatingAttributes.NET_PEER_PORT; @@ -87,6 +96,7 @@ import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isAwsSDKSpan; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isDBSpan; import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresent; +import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresentWithFallback; import com.amazonaws.arn.Arn; import io.opentelemetry.api.common.AttributeKey; @@ -122,15 +132,6 @@ * represent "outgoing" traffic, and {@link SpanKind#INTERNAL} spans are ignored. */ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator { - // ToDo: These two keys were deleted by upstream. Code need to be updated to capture the same - // information by using new keys. - // https://github.com/open-telemetry/semantic-conventions-java/blob/release/v1.28.0/semconv/src/main/java/io/opentelemetry/semconv/SemanticAttributes.java#L3784-L3795 - static final AttributeKey SERVER_SOCKET_ADDRESS = - io.opentelemetry.api.common.AttributeKey.stringKey("server.socket.address"); - - static final AttributeKey SERVER_SOCKET_PORT = - io.opentelemetry.api.common.AttributeKey.longKey("server.socket.port"); - private static final Logger logger = Logger.getLogger(AwsMetricAttributeGenerator.class.getName()); @@ -293,9 +294,11 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde } else if (isKeyPresent(span, FAAS_INVOKED_NAME) || isKeyPresent(span, FAAS_TRIGGER)) { remoteService = getRemoteService(span, FAAS_INVOKED_NAME); remoteOperation = getRemoteOperation(span, FAAS_TRIGGER); - } else if (isKeyPresent(span, MESSAGING_SYSTEM) || isKeyPresent(span, MESSAGING_OPERATION)) { + } else if (isKeyPresent(span, MESSAGING_SYSTEM) + || isKeyPresentWithFallback(span, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION)) { remoteService = getRemoteService(span, MESSAGING_SYSTEM); - remoteOperation = getRemoteOperation(span, MESSAGING_OPERATION); + remoteOperation = + getRemoteOperationWithFallback(span, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION); } else if (isKeyPresent(span, GRAPHQL_OPERATION_TYPE)) { remoteService = GRAPHQL; remoteOperation = getRemoteOperation(span, GRAPHQL_OPERATION_TYPE); @@ -772,7 +775,7 @@ private static Optional getSnsResourceNameFromArn(Optional strin * {address} attribute is retrieved in priority order: * - {@link SemanticAttributes#SERVER_ADDRESS}, * - {@link SemanticAttributes#NET_PEER_NAME}, - * - {@link SemanticAttributes#SERVER_SOCKET_ADDRESS} + * - {@link SemanticAttributes#NETWORK_PEER_ADDRESS} * - {@link SemanticAttributes#DB_CONNECTION_STRING}-Hostname * * @@ -780,7 +783,7 @@ private static Optional getSnsResourceNameFromArn(Optional strin * {port} attribute is retrieved in priority order: * - {@link SemanticAttributes#SERVER_PORT}, * - {@link SemanticAttributes#NET_PEER_PORT}, - * - {@link SemanticAttributes#SERVER_SOCKET_PORT} + * - {@link SemanticAttributes#NETWORK_PEER_PORT} * - {@link SemanticAttributes#DB_CONNECTION_STRING}-Port * * @@ -799,9 +802,9 @@ private static Optional getDbConnection(SpanData span) { String networkPeerAddress = span.getAttributes().get(NET_PEER_NAME); Long networkPeerPort = span.getAttributes().get(NET_PEER_PORT); dbConnection = buildDbConnection(networkPeerAddress, networkPeerPort); - } else if (isKeyPresent(span, SERVER_SOCKET_ADDRESS)) { - String serverSocketAddress = span.getAttributes().get(SERVER_SOCKET_ADDRESS); - Long serverSocketPort = span.getAttributes().get(SERVER_SOCKET_PORT); + } else if (isKeyPresent(span, NETWORK_PEER_ADDRESS)) { + String serverSocketAddress = span.getAttributes().get(NETWORK_PEER_ADDRESS); + Long serverSocketPort = span.getAttributes().get(NETWORK_PEER_PORT); dbConnection = buildDbConnection(serverSocketAddress, serverSocketPort); } else if (isKeyPresent(span, DB_CONNECTION_STRING)) { String connectionString = span.getAttributes().get(DB_CONNECTION_STRING); @@ -954,6 +957,15 @@ private static String getRemoteOperation(SpanData span, AttributeKey rem return remoteOperation; } + static String getRemoteOperationWithFallback( + SpanData span, AttributeKey remoteOpKey, AttributeKey remoteOpFallbackKey) { + String remoteOp = span.getAttributes().get(remoteOpKey); + if (remoteOp == null) { + return getRemoteOperation(span, remoteOpFallbackKey); + } + return remoteOp; + } + /** * If no db.operation attribute provided in the span, we use db.statement to compute a valid * remote operation in a best-effort manner. To do this, we take the first substring of the diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java index ebeb751acb..1627de190c 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java @@ -23,6 +23,7 @@ import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_REQUEST_METHOD; import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_TARGET; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingOperationTypeIncubatingValues.PROCESS; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.AWS_LAMBDA_FUNCTION_NAME_CONFIG; @@ -153,6 +154,23 @@ static boolean isKeyPresent(SpanData span, AttributeKey key) { return span.getAttributes().get(key) != null; } + static boolean isKeyPresentWithFallback( + SpanData span, AttributeKey key, AttributeKey fallbackKey) { + if (span.getAttributes().get(key) != null) { + return true; + } + return isKeyPresent(span, fallbackKey); + } + + static T getKeyValueWithFallback( + SpanData span, AttributeKey key, AttributeKey fallbackKey) { + T value = span.getAttributes().get(key); + if (value != null) { + return value; + } + return span.getAttributes().get(fallbackKey); + } + static boolean isAwsSDKSpan(SpanData span) { // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/instrumentation/aws-sdk/#common-attributes return "aws-api".equals(span.getAttributes().get(RPC_SYSTEM)); @@ -170,7 +188,8 @@ static boolean shouldGenerateDependencyMetricAttributes(SpanData span) { } static boolean isConsumerProcessSpan(SpanData spanData) { - String messagingOperation = spanData.getAttributes().get(MESSAGING_OPERATION); + String messagingOperation = + getKeyValueWithFallback(spanData, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION); return SpanKind.CONSUMER.equals(spanData.getKind()) && PROCESS.equals(messagingOperation); } @@ -192,7 +211,8 @@ static boolean isLocalRoot(SpanData spanData) { private static boolean isSqsReceiveMessageConsumerSpan(SpanData spanData) { String spanName = spanData.getName(); SpanKind spanKind = spanData.getKind(); - String messagingOperation = spanData.getAttributes().get(MESSAGING_OPERATION); + String messagingOperation = + getKeyValueWithFallback(spanData, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION); InstrumentationScopeInfo instrumentationScopeInfo = spanData.getInstrumentationScopeInfo(); return SQS_RECEIVE_MESSAGE_SPAN_NAME.equalsIgnoreCase(spanName) diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java index a9f272483f..81667f6313 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java @@ -38,6 +38,7 @@ import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_TARGET; import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_URL; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingOperationTypeIncubatingValues.PROCESS; import static io.opentelemetry.semconv.incubating.NetIncubatingAttributes.NET_PEER_NAME; @@ -1264,31 +1265,31 @@ public void testDBClientSpanWithRemoteResourceAttributes() { assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_IDENTIFIER)).isNull(); mockAttribute(NET_PEER_PORT, null); - // Validate behaviour of DB_NAME, SERVER_SOCKET_ADDRESS and SERVER_SOCKET_PORT exist, then + // Validate behaviour of DB_NAME, NETWORK_PEER_ADDRESS and NETWORK_PEER_PORT exist, then // remove it. mockAttribute(DB_NAME, "db_name"); - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_ADDRESS, "abc.com"); - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_PORT, 3306L); + mockAttribute(NETWORK_PEER_ADDRESS, "abc.com"); + mockAttribute(NETWORK_PEER_PORT, 3306L); validateRemoteResourceAttributes("DB::Connection", "db_name|abc.com|3306"); mockAttribute(DB_NAME, null); - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_ADDRESS, null); - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_PORT, null); + mockAttribute(NETWORK_PEER_ADDRESS, null); + mockAttribute(NETWORK_PEER_PORT, null); - // Validate behaviour of DB_NAME, SERVER_SOCKET_ADDRESS exist, then remove it. + // Validate behaviour of DB_NAME, NETWORK_PEER_ADDRESS exist, then remove it. mockAttribute(DB_NAME, "db_name"); - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_ADDRESS, "abc.com"); + mockAttribute(NETWORK_PEER_ADDRESS, "abc.com"); validateRemoteResourceAttributes("DB::Connection", "db_name|abc.com"); mockAttribute(DB_NAME, null); - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_ADDRESS, null); + mockAttribute(NETWORK_PEER_ADDRESS, null); - // Validate behaviour of SERVER_SOCKET_PORT exist, then remove it. - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_PORT, 3306L); + // Validate behaviour of NETWORK_PEER_PORT exist, then remove it. + mockAttribute(NETWORK_PEER_PORT, 3306L); when(spanDataMock.getKind()).thenReturn(SpanKind.CLIENT); actualAttributes = GENERATOR.generateMetricAttributeMapFromSpan(spanDataMock, resource).get(DEPENDENCY_METRIC); assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_TYPE)).isNull(); assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_IDENTIFIER)).isNull(); - mockAttribute(AwsMetricAttributeGenerator.SERVER_SOCKET_PORT, null); + mockAttribute(NETWORK_PEER_PORT, null); // Validate behaviour of only DB_NAME exist, then remove it. mockAttribute(DB_NAME, "db_name"); @@ -1614,6 +1615,36 @@ public void testDbUserPresentAndIsDbSpanFalse() { assertThat(actualAttributes.get(AWS_REMOTE_DB_USER)).isNull(); } + @Test + public void testGetRemoteOperationWithFallback_NewKeyPresent() { + mockAttribute(MESSAGING_OPERATION_TYPE, "send"); + mockAttribute(MESSAGING_OPERATION, "publish"); + String result = + AwsMetricAttributeGenerator.getRemoteOperationWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION); + + assertThat(result).isEqualTo("send"); + } + + @Test + public void testGetRemoteOperationWithFallback_DeprecatedKeyPresent() { + mockAttribute(MESSAGING_OPERATION, "publish"); + String result = + AwsMetricAttributeGenerator.getRemoteOperationWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION); + + assertThat(result).isEqualTo("publish"); + } + + @Test + public void testGetRemoteOperationWithFallback_BothKeysAbsent() { + String result = + AwsMetricAttributeGenerator.getRemoteOperationWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION); + + assertThat(result).isEqualTo(UNKNOWN_REMOTE_OPERATION); + } + @Test public void testNormalizeRemoteServiceName_NoNormalization() { String serviceName = "non aws service"; diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java index 798f3f060a..d4675674dc 100644 --- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java +++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java @@ -18,6 +18,7 @@ import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; import static io.opentelemetry.semconv.UrlAttributes.URL_PATH; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingOperationTypeIncubatingValues.PROCESS; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingOperationTypeIncubatingValues.RECEIVE; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; @@ -365,6 +366,14 @@ public void testIsConsumerProcessSpanFalse() { assertThat(AwsSpanProcessingUtil.isConsumerProcessSpan(spanDataMock)).isFalse(); } + @Test + public void testIsConsumerProcessSpanFalse_with_MESSAGING_OPERATION_TYPE() { + when(attributesMock.get(MESSAGING_OPERATION_TYPE)).thenReturn(RECEIVE); + when(attributesMock.get(MESSAGING_OPERATION)).thenReturn(PROCESS); + when(spanDataMock.getKind()).thenReturn(SpanKind.CONSUMER); + assertThat(AwsSpanProcessingUtil.isConsumerProcessSpan(spanDataMock)).isFalse(); + } + @Test public void testIsConsumerProcessSpanTrue() { when(attributesMock.get(MESSAGING_OPERATION)).thenReturn(PROCESS); @@ -372,6 +381,14 @@ public void testIsConsumerProcessSpanTrue() { assertThat(AwsSpanProcessingUtil.isConsumerProcessSpan(spanDataMock)).isTrue(); } + @Test + public void testIsConsumerProcessSpanTrue_with_MESSAGING_OPERATION_TYPE() { + when(attributesMock.get(MESSAGING_OPERATION_TYPE)).thenReturn(PROCESS); + when(attributesMock.get(MESSAGING_OPERATION)).thenReturn(RECEIVE); + when(spanDataMock.getKind()).thenReturn(SpanKind.CONSUMER); + assertThat(AwsSpanProcessingUtil.isConsumerProcessSpan(spanDataMock)).isTrue(); + } + // check that AWS SDK v1 SQS ReceiveMessage consumer spans metrics are suppressed @Test public void testNoMetricAttributesForSqsConsumerSpanAwsSdkV1() { @@ -436,6 +453,26 @@ public void testNoMetricAttributesForAwsSdkSqsConsumerProcessSpan() { .isTrue(); } + @Test + public void + testNoMetricAttributesForAwsSdkSqsConsumerProcessSpan_with_MESSAGING_OPERATION_TYPE() { + InstrumentationScopeInfo instrumentationScopeInfo = mock(InstrumentationScopeInfo.class); + when(instrumentationScopeInfo.getName()).thenReturn("io.opentelemetry.aws-sdk-2.2"); + when(spanDataMock.getInstrumentationScopeInfo()).thenReturn(instrumentationScopeInfo); + when(spanDataMock.getKind()).thenReturn(SpanKind.CONSUMER); + when(spanDataMock.getName()).thenReturn("Sqs.ReceiveMessage"); + when(attributesMock.get(MESSAGING_OPERATION_TYPE)).thenReturn(PROCESS); + + assertThat(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).isFalse(); + assertThat(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)) + .isFalse(); + + when(attributesMock.get(MESSAGING_OPERATION_TYPE)).thenReturn(RECEIVE); + assertThat(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).isTrue(); + assertThat(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)) + .isTrue(); + } + @Test public void testSqlDialectKeywordsOrder() { List keywords = getDialectKeywords(); @@ -454,4 +491,57 @@ public void testSqlDialectKeywordsMaxLength() { assertThat(MAX_KEYWORD_LENGTH >= keyword.length()); } } + + @Test + public void testIsKeyPresentWithFallback_NewKeyPresent() { + when(attributesMock.get(MESSAGING_OPERATION_TYPE)).thenReturn("publish"); + assertThat( + AwsSpanProcessingUtil.isKeyPresentWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION)) + .isTrue(); + } + + @Test + public void testIsKeyPresentWithFallback_DeprecatedKeyPresent() { + when(attributesMock.get(MESSAGING_OPERATION)).thenReturn("publish"); + assertThat( + AwsSpanProcessingUtil.isKeyPresentWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION)) + .isTrue(); + } + + @Test + public void testIsKeyPresentWithFallback_BothKeysAbsent() { + assertThat( + AwsSpanProcessingUtil.isKeyPresentWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION)) + .isFalse(); + } + + @Test + public void testGetKeyValueWithFallback_NewKeyPresent() { + when(attributesMock.get(MESSAGING_OPERATION_TYPE)).thenReturn("send"); + when(attributesMock.get(MESSAGING_OPERATION)).thenReturn("publish"); + assertThat( + AwsSpanProcessingUtil.getKeyValueWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION)) + .isEqualTo("send"); + } + + @Test + public void testGetKeyValueWithFallback_DeprecatedKeyPresent() { + when(attributesMock.get(MESSAGING_OPERATION)).thenReturn("publish"); + assertThat( + AwsSpanProcessingUtil.getKeyValueWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION)) + .isEqualTo("publish"); + } + + @Test + public void testGetKeyValueWithFallback_BothKeysAbsent() { + assertThat( + AwsSpanProcessingUtil.getKeyValueWithFallback( + spanDataMock, MESSAGING_OPERATION_TYPE, MESSAGING_OPERATION)) + .isNull(); + } }