This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new d13701ef26b NIFI-16113 Added Processor 
ChangeSQSMessageVisibilityTimeout for AWS (#11430)
d13701ef26b is described below

commit d13701ef26bca8f3d9bb419b184785ecfb0c7860
Author: pkelly-nifi <[email protected]>
AuthorDate: Mon Aug 10 13:33:37 2026 +0000

    NIFI-16113 Added Processor ChangeSQSMessageVisibilityTimeout for AWS 
(#11430)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../aws/sqs/ChangeSQSMessageVisibilityTimeout.java | 141 +++++++++++++++++++++
 .../services/org.apache.nifi.processor.Processor   |   1 +
 .../sqs/ChangeSQSMessageVisibilityTimeoutIT.java   |  65 ++++++++++
 .../sqs/ChangeSQSMessageVisibilityTimeoutTest.java | 131 +++++++++++++++++++
 4 files changed, 338 insertions(+)

diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeout.java
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeout.java
new file mode 100644
index 00000000000..552b9c1f6dd
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeout.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.processors.aws.sqs;
+
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
+import org.apache.nifi.annotation.behavior.SupportsBatching;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.SeeAlso;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.aws.AbstractAwsSyncProcessor;
+import software.amazon.awssdk.services.sqs.SqsClient;
+import software.amazon.awssdk.services.sqs.SqsClientBuilder;
+import 
software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequest;
+import 
software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequestEntry;
+import 
software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchResponse;
+
+import java.util.List;
+
+import static org.apache.nifi.processors.aws.region.RegionUtil.CUSTOM_REGION;
+import static org.apache.nifi.processors.aws.region.RegionUtil.REGION;
+
+@SupportsBatching
+@SeeAlso({GetSQS.class, PutSQS.class})
+@InputRequirement(Requirement.INPUT_REQUIRED)
+@Tags({"Amazon", "AWS", "SQS", "Queue", "Update"})
+@CapabilityDescription("Updates the visibility timeout for a message from an 
Amazon Simple Queuing Service Queue")
+public class ChangeSQSMessageVisibilityTimeout extends 
AbstractAwsSyncProcessor<SqsClient, SqsClientBuilder> {
+
+    static final PropertyDescriptor QUEUE_URL = new 
PropertyDescriptor.Builder()
+            .name("Queue URL")
+            .description("The URL of the queue for which the message's 
visibility timeout should be updated")
+            .addValidator(StandardValidators.URL_VALIDATOR)
+            
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+            .required(true)
+            .build();
+
+    static final PropertyDescriptor RECEIPT_HANDLE = new 
PropertyDescriptor.Builder()
+            .name("Receipt Handle")
+            .description("The identifier that specifies the receipt of the 
message")
+            
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .defaultValue("${sqs.receipt.handle}")
+            .build();
+
+    static final PropertyDescriptor VISIBILITY_TIMEOUT = new 
PropertyDescriptor.Builder()
+            .name("Visibility Timeout")
+            .description("The amount of time after a message is received but 
not deleted that the message is hidden from other consumers")
+            
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+            .required(true)
+            .defaultValue("15 mins")
+            .addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
+            .build();
+
+    private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
List.of(
+        QUEUE_URL,
+        VISIBILITY_TIMEOUT,
+        REGION,
+        CUSTOM_REGION,
+        AWS_CREDENTIALS_PROVIDER_SERVICE,
+        SSL_CONTEXT_SERVICE,
+        RECEIPT_HANDLE,
+        TIMEOUT,
+        ENDPOINT_OVERRIDE,
+        PROXY_CONFIGURATION_SERVICE
+    );
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return PROPERTY_DESCRIPTORS;
+    }
+
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession 
session) {
+        FlowFile flowFile = session.get();
+        if (flowFile == null) {
+            return;
+        }
+
+        final String queueUrl = 
context.getProperty(QUEUE_URL).evaluateAttributeExpressions(flowFile).getValue();
+
+        final SqsClient client = getClient(context);
+
+        final String receiptHandle = 
context.getProperty(RECEIPT_HANDLE).evaluateAttributeExpressions(flowFile).getValue();
+        final int visibilityTimeout = 
Math.toIntExact(context.getProperty(VISIBILITY_TIMEOUT).evaluateAttributeExpressions(flowFile).asDuration().toSeconds());
+        final String entryId = 
flowFile.getAttribute(CoreAttributes.UUID.key());
+        final ChangeMessageVisibilityBatchRequestEntry entry = 
ChangeMessageVisibilityBatchRequestEntry.builder()
+                .receiptHandle(receiptHandle)
+                .id(entryId)
+                .visibilityTimeout(visibilityTimeout)
+                .build();
+
+        final ChangeMessageVisibilityBatchRequest request = 
ChangeMessageVisibilityBatchRequest.builder()
+                .queueUrl(queueUrl)
+                .entries(entry)
+                .build();
+
+        try {
+            final ChangeMessageVisibilityBatchResponse response = 
client.changeMessageVisibilityBatch(request);
+
+            if (response.failed().isEmpty()) {
+                getLogger().info("Successfully updated visibility timeout to 
{} for SQS message for {}", visibilityTimeout, flowFile);
+                session.transfer(flowFile, REL_SUCCESS);
+            } else {
+                getLogger().error("Error updating visibility timeout for {}: 
{}", flowFile, response.failed().getFirst().toString());
+                session.transfer(flowFile, REL_FAILURE);
+            }
+        } catch (final Exception e) {
+            getLogger().error("Failed to update visibility timeout for SQS 
message for {}: {}", flowFile, e);
+            flowFile = session.penalize(flowFile);
+            session.transfer(flowFile, REL_FAILURE);
+        }
+    }
+
+    @Override
+    protected SqsClientBuilder createClientBuilder(final ProcessContext 
context) {
+        return SqsClient.builder();
+    }
+}
diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index b8885827c0c..eb230a1e547 100644
--- 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -22,6 +22,7 @@ org.apache.nifi.processors.aws.s3.DeleteS3Object
 org.apache.nifi.processors.aws.s3.TagS3Object
 org.apache.nifi.processors.aws.s3.ListS3
 org.apache.nifi.processors.aws.sns.PutSNS
+org.apache.nifi.processors.aws.sqs.ChangeSQSMessageVisibilityTimeout
 org.apache.nifi.processors.aws.sqs.GetSQS
 org.apache.nifi.processors.aws.sqs.PutSQS
 org.apache.nifi.processors.aws.sqs.DeleteSQS
diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeoutIT.java
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeoutIT.java
new file mode 100644
index 00000000000..4492fc67494
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeoutIT.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.processors.aws.sqs;
+
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.util.TestRunner;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.sqs.model.Message;
+import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
+import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse;
+import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
+import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ChangeSQSMessageVisibilityTimeoutIT extends AbstractSQSIT {
+
+    @Test
+    void testSimpleUpdate() {
+        final SendMessageRequest request = SendMessageRequest.builder()
+                .queueUrl(getQueueUrl())
+                .messageBody("Hello World")
+                .build();
+        final SendMessageResponse response = getClient().sendMessage(request);
+        assertTrue(response.sdkHttpResponse().isSuccessful());
+
+        // Setup - receive message to get receipt handle
+        final ReceiveMessageRequest receiveMessageRequest = 
ReceiveMessageRequest.builder()
+                .queueUrl(getQueueUrl())
+                .build();
+        final ReceiveMessageResponse receiveMessageResult = 
getClient().receiveMessage(receiveMessageRequest);
+        assertEquals(200, receiveMessageResult.sdkHttpResponse().statusCode());
+        final Message updateMessage = 
receiveMessageResult.messages().getFirst();
+        final String receiptHandle = updateMessage.receiptHandle();
+
+        // Test - update message with ChangeSQSMessageVisibilityTimeout
+        final TestRunner runner = 
initRunner(ChangeSQSMessageVisibilityTimeout.class);
+        final Map<String, String> ffAttributes = new HashMap<>();
+        ffAttributes.put(CoreAttributes.FILENAME.key(), "1.txt");
+        ffAttributes.put("sqs.receipt.handle", receiptHandle);
+        runner.enqueue("TestMessageBody", ffAttributes);
+
+        runner.run(1);
+
+        
runner.assertAllFlowFilesTransferred(ChangeSQSMessageVisibilityTimeout.REL_SUCCESS,
 1);
+    }
+}
diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeoutTest.java
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeoutTest.java
new file mode 100644
index 00000000000..b0c3fd84ab2
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/sqs/ChangeSQSMessageVisibilityTimeoutTest.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.processors.aws.sqs;
+
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processors.aws.testutil.AuthUtils;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import software.amazon.awssdk.services.sqs.SqsClient;
+import 
software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequest;
+import 
software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchResponse;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ChangeSQSMessageVisibilityTimeoutTest {
+
+    private TestRunner runner = null;
+    private SqsClient mockSQSClient = null;
+
+    private static final String QUEUE_URL = 
"https://sqs.us-west-2.amazonaws.com/123456789012/test-queue-000000000";;
+    private static final String FILENAME = "1.txt";
+    private static final String RECEIPT_HANDLE = "test-receipt-handle-1";
+
+    @BeforeEach
+    void setUp() {
+        mockSQSClient = mock(SqsClient.class);
+        ChangeMessageVisibilityBatchResponse mockResponse = 
ChangeMessageVisibilityBatchResponse.builder()
+                .failed(Collections.emptyList())
+                .build();
+        
when(mockSQSClient.changeMessageVisibilityBatch(any(ChangeMessageVisibilityBatchRequest.class))).thenReturn(mockResponse);
+        ChangeSQSMessageVisibilityTimeout 
mockChangeSQSMessageVisibilityTimeout = new ChangeSQSMessageVisibilityTimeout() 
{
+
+            @Override
+            protected SqsClient getClient(ProcessContext context) {
+                return mockSQSClient;
+            }
+        };
+        runner = 
TestRunners.newTestRunner(mockChangeSQSMessageVisibilityTimeout);
+        AuthUtils.enableAccessKey(runner, "accessKeyId", "secretKey");
+    }
+
+    @Test
+    void testChangeVisibilityTimeoutSingleMessage() {
+        runner.setProperty(ChangeSQSMessageVisibilityTimeout.QUEUE_URL, 
QUEUE_URL);
+        final Map<String, String> ffAttributes = new HashMap<>();
+        ffAttributes.put(CoreAttributes.FILENAME.key(), FILENAME);
+        ffAttributes.put("sqs.receipt.handle", RECEIPT_HANDLE);
+        runner.enqueue("TestMessageBody", ffAttributes);
+
+        runner.assertValid();
+        runner.run(1);
+
+        ArgumentCaptor<ChangeMessageVisibilityBatchRequest> 
captureChangeMessageVisibilityTimeoutRequest = 
ArgumentCaptor.forClass(ChangeMessageVisibilityBatchRequest.class);
+        verify(mockSQSClient, 
times(1)).changeMessageVisibilityBatch(captureChangeMessageVisibilityTimeoutRequest.capture());
+        ChangeMessageVisibilityBatchRequest 
changeMessageVisibilityTimeoutRequest = 
captureChangeMessageVisibilityTimeoutRequest.getValue();
+        assertEquals(QUEUE_URL, 
changeMessageVisibilityTimeoutRequest.queueUrl());
+        assertEquals(RECEIPT_HANDLE, 
changeMessageVisibilityTimeoutRequest.entries().getFirst().receiptHandle());
+        assertEquals(15 * 60, 
changeMessageVisibilityTimeoutRequest.entries().getFirst().visibilityTimeout());
+
+        
runner.assertAllFlowFilesTransferred(ChangeSQSMessageVisibilityTimeout.REL_SUCCESS,
 1);
+    }
+
+    @Test
+    void testChangeVisibilityTimeoutWithCustomReceiptHandle() {
+        runner.setProperty(ChangeSQSMessageVisibilityTimeout.QUEUE_URL, 
QUEUE_URL);
+        runner.setProperty(ChangeSQSMessageVisibilityTimeout.RECEIPT_HANDLE, 
"${custom.receipt.handle}");
+        
runner.setProperty(ChangeSQSMessageVisibilityTimeout.VISIBILITY_TIMEOUT, 
"${custom.timeout.value}");
+        final Map<String, String> ffAttributes = new HashMap<>();
+        ffAttributes.put(CoreAttributes.FILENAME.key(), FILENAME);
+        ffAttributes.put("custom.receipt.handle", RECEIPT_HANDLE);
+        ffAttributes.put("custom.timeout.value", "1 min");
+        runner.enqueue("TestMessageBody", ffAttributes);
+
+        runner.assertValid();
+        runner.run(1);
+
+        ArgumentCaptor<ChangeMessageVisibilityBatchRequest> 
captureChangeMessageVisibilityRequest = 
ArgumentCaptor.forClass(ChangeMessageVisibilityBatchRequest.class);
+        verify(mockSQSClient, 
times(1)).changeMessageVisibilityBatch(captureChangeMessageVisibilityRequest.capture());
+        ChangeMessageVisibilityBatchRequest changeMessageVisibilityRequest = 
captureChangeMessageVisibilityRequest.getValue();
+        assertEquals(60, 
changeMessageVisibilityRequest.entries().getFirst().visibilityTimeout());
+
+        
runner.assertAllFlowFilesTransferred(ChangeSQSMessageVisibilityTimeout.REL_SUCCESS,
 1);
+    }
+
+    @Test
+    void testChangeMessageVisibilityTimeoutException() {
+        runner.setProperty(ChangeSQSMessageVisibilityTimeout.QUEUE_URL, 
QUEUE_URL);
+        final Map<String, String> ff1Attributes = new HashMap<>();
+        ff1Attributes.put(CoreAttributes.FILENAME.key(), FILENAME);
+        ff1Attributes.put("sqs.receipt.handle", RECEIPT_HANDLE);
+        runner.enqueue("TestMessageBody1", ff1Attributes);
+        
when(mockSQSClient.changeMessageVisibilityBatch(any(ChangeMessageVisibilityBatchRequest.class)))
+                .thenThrow(new RuntimeException());
+
+        runner.assertValid();
+        runner.run(1);
+
+        ArgumentCaptor<ChangeMessageVisibilityBatchRequest> 
captureChangeVisibilityTimeoutRequest = 
ArgumentCaptor.forClass(ChangeMessageVisibilityBatchRequest.class);
+        verify(mockSQSClient, 
times(1)).changeMessageVisibilityBatch(captureChangeVisibilityTimeoutRequest.capture());
+
+        
runner.assertAllFlowFilesTransferred(ChangeSQSMessageVisibilityTimeout.REL_FAILURE,
 1);
+    }
+
+}

Reply via email to