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

joewitt 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 80e889305f NIFI-7085 add flowFile batching to ConsumeJMS and 
PublishJMS This closes #8584
80e889305f is described below

commit 80e889305ff15ce913545d84b37676a4ceb821ec
Author: Mike Moser <[email protected]>
AuthorDate: Mon Mar 18 14:31:40 2024 +0000

    NIFI-7085 add flowFile batching to ConsumeJMS and PublishJMS
    This closes #8584
    
    load some configuration in onScheduled instead of a loop, reinstated and 
fixed a failing integration test
---
 .../nifi/jms/processors/AbstractJMSProcessor.java  |   8 ++
 .../org/apache/nifi/jms/processors/ConsumeJMS.java |  65 +++++++-----
 .../apache/nifi/jms/processors/JMSConsumer.java    |  24 +----
 .../apache/nifi/jms/processors/JMSPublisher.java   |   2 +-
 .../org/apache/nifi/jms/processors/PublishJMS.java |  36 +++++--
 .../apache/nifi/jms/processors/ConsumeJMSIT.java   |   4 +
 .../jms/processors/JMSPublisherConsumerIT.java     | 112 ++++++++++++++++-----
 .../apache/nifi/processors/standard/GetFile.java   |   2 +-
 8 files changed, 169 insertions(+), 84 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/AbstractJMSProcessor.java
 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/AbstractJMSProcessor.java
index 0d98616871..b5f958598e 100644
--- 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/AbstractJMSProcessor.java
+++ 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/AbstractJMSProcessor.java
@@ -128,6 +128,14 @@ public abstract class AbstractJMSProcessor<T extends 
JMSWorker> extends Abstract
             
.identifiesControllerService(JMSConnectionFactoryProviderDefinition.class)
             .build();
 
+    static final PropertyDescriptor MAX_BATCH_SIZE = new 
PropertyDescriptor.Builder()
+            .name("Maximum Batch Size")
+            .description("The maximum number of messages to publish or consume 
in each invocation of the processor.")
+            .required(true)
+            .defaultValue("1")
+            .addValidator(StandardValidators.createLongValidator(1, 10_000, 
true))
+            .build();
+
     static final List<PropertyDescriptor> JNDI_JMS_CF_PROPERTIES = 
Collections.unmodifiableList(
             
JndiJmsConnectionFactoryProperties.getPropertyDescriptors().stream()
                     .map(pd -> new PropertyDescriptor.Builder()
diff --git 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/ConsumeJMS.java
 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/ConsumeJMS.java
index 87444869bc..ce50821515 100644
--- 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/ConsumeJMS.java
+++ 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/ConsumeJMS.java
@@ -40,6 +40,7 @@ import 
org.apache.nifi.jms.processors.ioconcept.writer.FlowFileWriter;
 import org.apache.nifi.jms.processors.ioconcept.writer.FlowFileWriterCallback;
 import org.apache.nifi.jms.processors.ioconcept.writer.record.OutputStrategy;
 import org.apache.nifi.jms.processors.ioconcept.writer.record.RecordWriter;
+import org.apache.nifi.migration.PropertyConfiguration;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
 import org.apache.nifi.processor.Relationship;
@@ -251,6 +252,7 @@ public class ConsumeJMS extends 
AbstractJMSProcessor<JMSConsumer> {
         _propertyDescriptors.add(SHARED_SUBSCRIBER);
         _propertyDescriptors.add(SUBSCRIPTION_NAME);
         _propertyDescriptors.add(TIMEOUT);
+        _propertyDescriptors.add(MAX_BATCH_SIZE);
         _propertyDescriptors.add(ERROR_QUEUE);
 
         _propertyDescriptors.add(RECORD_READER);
@@ -268,6 +270,17 @@ public class ConsumeJMS extends 
AbstractJMSProcessor<JMSConsumer> {
         relationships = Collections.unmodifiableSet(_relationships);
     }
 
+    @Override
+    public void migrateProperties(PropertyConfiguration config) {
+        super.migrateProperties(config);
+
+        if (!config.hasProperty(MAX_BATCH_SIZE)) {
+            if (config.isPropertySet(BASE_RECORD_READER)) {
+                config.setProperty(MAX_BATCH_SIZE, "10000");
+            }
+        }
+    }
+
     private static boolean isDurableSubscriber(final ProcessContext context) {
         final Boolean durableBoolean = 
context.getProperty(DURABLE_SUBSCRIBER).evaluateAttributeExpressions().asBoolean();
         return durableBoolean == null ? false : durableBoolean;
@@ -322,9 +335,9 @@ public class ConsumeJMS extends 
AbstractJMSProcessor<JMSConsumer> {
 
         try {
             if (context.getProperty(RECORD_READER).isSet()) {
-                processMessageSet(context, processSession, consumer, 
destinationName, errorQueueName, durable, shared, subscriptionName, 
messageSelector, charset);
+                processMessagesAsRecords(context, processSession, consumer, 
destinationName, errorQueueName, durable, shared, subscriptionName, 
messageSelector, charset);
             } else {
-                processSingleMessage(processSession, consumer, 
destinationName, errorQueueName, durable, shared, subscriptionName, 
messageSelector, charset);
+                processMessages(context, processSession, consumer, 
destinationName, errorQueueName, durable, shared, subscriptionName, 
messageSelector, charset);
             }
         } catch (Exception e) {
             getLogger().error("Error while trying to process JMS message", e);
@@ -334,26 +347,25 @@ public class ConsumeJMS extends 
AbstractJMSProcessor<JMSConsumer> {
         }
     }
 
-    private void processSingleMessage(ProcessSession processSession, 
JMSConsumer consumer, String destinationName, String errorQueueName,
-                                      boolean durable, boolean shared, String 
subscriptionName, String messageSelector, String charset) {
-
-        consumer.consumeSingleMessage(destinationName, errorQueueName, 
durable, shared, subscriptionName, messageSelector, charset, response -> {
-            if (response == null) {
-                return;
-            }
-
-            try {
-                final FlowFile flowFile = 
createFlowFileFromMessage(processSession, destinationName, response);
-
-                processSession.getProvenanceReporter().receive(flowFile, 
destinationName);
-                processSession.transfer(flowFile, REL_SUCCESS);
-                processSession.commitAsync(
-                        () -> withLog(() -> acknowledge(response)),
-                        __ -> withLog(() -> response.reject()));
-            } catch (final Throwable t) {
-                response.reject();
-                throw t;
-            }
+    private void processMessages(ProcessContext context, ProcessSession 
processSession, JMSConsumer consumer, String destinationName, String 
errorQueueName,
+                                 boolean durable, boolean shared, String 
subscriptionName, String messageSelector, String charset) {
+
+        int batchSize = context.getProperty(MAX_BATCH_SIZE).asInteger();
+        consumer.consumeMessageSet(destinationName, errorQueueName, durable, 
shared, subscriptionName, messageSelector, charset, batchSize, jmsResponses -> {
+            jmsResponses.forEach(response -> {
+                try {
+                    final FlowFile flowFile = 
createFlowFileFromMessage(processSession, destinationName, response);
+
+                    processSession.getProvenanceReporter().receive(flowFile, 
destinationName);
+                    processSession.transfer(flowFile, REL_SUCCESS);
+                    processSession.commitAsync(
+                            () -> withLog(() -> acknowledge(response)),
+                            __ -> withLog(() -> response.reject()));
+                } catch (final Throwable t) {
+                    response.reject();
+                    throw t;
+                }
+            });
         });
     }
 
@@ -369,9 +381,10 @@ public class ConsumeJMS extends 
AbstractJMSProcessor<JMSConsumer> {
         return processSession.putAllAttributes(flowFile, attributes);
     }
 
-    private void processMessageSet(ProcessContext context, ProcessSession 
session, JMSConsumer consumer, String destinationName, String errorQueueName,
-                                   boolean durable, boolean shared, String 
subscriptionName, String messageSelector, String charset) {
+    private void processMessagesAsRecords(ProcessContext context, 
ProcessSession session, JMSConsumer consumer, String destinationName, String 
errorQueueName,
+                                          boolean durable, boolean shared, 
String subscriptionName, String messageSelector, String charset) {
 
+        int batchSize = context.getProperty(MAX_BATCH_SIZE).asInteger();
         final RecordReaderFactory readerFactory = 
context.getProperty(RECORD_READER).asControllerService(RecordReaderFactory.class);
         final RecordSetWriterFactory writerFactory = 
context.getProperty(RECORD_WRITER).asControllerService(RecordSetWriterFactory.class);
         final OutputStrategy outputStrategy = 
OutputStrategy.valueOf(context.getProperty(OUTPUT_STRATEGY).getValue());
@@ -385,7 +398,7 @@ public class ConsumeJMS extends 
AbstractJMSProcessor<JMSConsumer> {
                 getLogger()
         );
 
-        consumer.consumeMessageSet(destinationName, errorQueueName, durable, 
shared, subscriptionName, messageSelector, charset, jmsResponses -> {
+        consumer.consumeMessageSet(destinationName, errorQueueName, durable, 
shared, subscriptionName, messageSelector, charset, batchSize, jmsResponses -> {
             flowFileWriter.write(session, jmsResponses, new 
FlowFileWriterCallback<>() {
                 @Override
                 public void onSuccess(FlowFile flowFile, List<JMSResponse> 
processedMessages, List<JMSResponse> failedMessages) {
@@ -477,7 +490,7 @@ public class ConsumeJMS extends 
AbstractJMSProcessor<JMSConsumer> {
      * Use provided clientId for non shared durable consumers, if not set
      * always a different value as defined in {@link 
AbstractJMSProcessor#setClientId(ProcessContext, SingleConnectionFactory)}.
      * </p>
-     * See {@link Session#createDurableConsumer(javax.jms.Topic, String, 
String, boolean)},
+     * See {@link Session#createDurableConsumer(jakarta.jms.Topic, String, 
String, boolean)},
      * in special following part: <i>An unshared durable subscription is
      * identified by a name specified by the client and by the client 
identifier,
      * which must be set. An application which subsequently wishes to create
diff --git 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSConsumer.java
 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSConsumer.java
index 70f7fa4824..783415c200 100644
--- 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSConsumer.java
+++ 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSConsumer.java
@@ -52,8 +52,6 @@ import java.util.function.Consumer;
  */
 class JMSConsumer extends JMSWorker {
 
-    private final static int MAX_MESSAGES_PER_FLOW_FILE = 10000;
-
     JMSConsumer(CachingConnectionFactory connectionFactory, JmsTemplate 
jmsTemplate, ComponentLog logger) {
         super(connectionFactory, jmsTemplate, logger);
         logger.debug("Created Message Consumer for '{}'", jmsTemplate);
@@ -88,29 +86,11 @@ class JMSConsumer extends JMSWorker {
         }
     }
 
-    /**
-     * Receives a message from the broker. It is the consumerCallback's 
responsibility to acknowledge the received message.
-     */
-    public void consumeSingleMessage(final String destinationName, String 
errorQueueName, final boolean durable, final boolean shared, final String 
subscriptionName, final String messageSelector,
-                                     final String charset, final 
Consumer<JMSResponse> singleMessageConsumer) {
-        doWithJmsTemplate(destinationName, durable, shared, subscriptionName, 
messageSelector, (session, messageConsumer) -> {
-            final JMSResponse response = receiveMessage(session, 
messageConsumer, charset, errorQueueName);
-            if (response != null) {
-                // Provide the JMSResponse to the processor to handle. It is 
the responsibility of the
-                // processor to handle acknowledgment of the message (if 
Client Acknowledge), and it is
-                // the responsibility of the processor to handle closing the 
Message Consumer.
-                // Both of these actions can be handled by calling the 
acknowledge() or reject() methods of
-                // the JMSResponse.
-                singleMessageConsumer.accept(response);
-            }
-        });
-    }
-
     /**
      * Receives a list of messages from the broker. It is the 
consumerCallback's responsibility to acknowledge the received message.
      */
     public void consumeMessageSet(final String destinationName, String 
errorQueueName, final boolean durable, final boolean shared, final String 
subscriptionName, final String messageSelector,
-                        final String charset, final 
Consumer<List<JMSResponse>> messageSetConsumer) {
+                                  final String charset, final int batchSize, 
final Consumer<List<JMSResponse>> messageSetConsumer) {
         doWithJmsTemplate(destinationName, durable, shared, subscriptionName, 
messageSelector, new MessageReceiver() {
             @Override
             public void consume(Session session, MessageConsumer 
messageConsumer) throws JMSException {
@@ -118,7 +98,7 @@ class JMSConsumer extends JMSWorker {
                 int batchCounter = 0;
 
                 JMSResponse response;
-                while ((response = receiveMessage(session, messageConsumer, 
charset, errorQueueName)) != null && batchCounter < MAX_MESSAGES_PER_FLOW_FILE) 
{
+                while (batchCounter < batchSize && (response = 
receiveMessage(session, messageConsumer, charset, errorQueueName)) != null) {
                     response.setBatchOrder(batchCounter);
                     jmsResponses.add(response);
                     batchCounter++;
diff --git 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSPublisher.java
 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSPublisher.java
index 8d301bbbf8..f84c21c1f4 100644
--- 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSPublisher.java
+++ 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/JMSPublisher.java
@@ -153,7 +153,7 @@ class JMSPublisher extends JMSWorker {
     }
 
     /**
-     * Implementations of this interface use {@link javax.jms.Message} methods 
to set strongly typed properties.
+     * Implementations of this interface use {@link jakarta.jms.Message} 
methods to set strongly typed properties.
      */
     public interface JmsPropertySetter {
         void setProperty(final Message message, final String name, final 
String value) throws JMSException, NumberFormatException;
diff --git 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/PublishJMS.java
 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/PublishJMS.java
index 411fb404ff..693e58bc8f 100644
--- 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/PublishJMS.java
+++ 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/processors/PublishJMS.java
@@ -29,6 +29,7 @@ import 
org.apache.nifi.annotation.behavior.SystemResourceConsideration;
 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.annotation.lifecycle.OnScheduled;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.RequiredPermission;
 import org.apache.nifi.expression.ExpressionLanguageScope;
@@ -183,6 +184,7 @@ public class PublishJMS extends 
AbstractJMSProcessor<JMSPublisher> {
         _propertyDescriptors.add(CHARSET);
         _propertyDescriptors.add(ALLOW_ILLEGAL_HEADER_CHARS);
         _propertyDescriptors.add(ATTRIBUTES_AS_HEADERS_REGEX);
+        _propertyDescriptors.add(MAX_BATCH_SIZE);
 
         _propertyDescriptors.add(RECORD_READER);
         _propertyDescriptors.add(RECORD_WRITER);
@@ -198,6 +200,22 @@ public class PublishJMS extends 
AbstractJMSProcessor<JMSPublisher> {
         relationships = Collections.unmodifiableSet(_relationships);
     }
 
+    volatile Boolean allowIllegalChars;
+    volatile Pattern attributeHeaderPattern;
+    volatile RecordReaderFactory readerFactory;
+    volatile RecordSetWriterFactory writerFactory;
+
+    @OnScheduled
+    public void onScheduled(final ProcessContext context) {
+        allowIllegalChars = 
context.getProperty(ALLOW_ILLEGAL_HEADER_CHARS).asBoolean();
+
+        final String attributeHeaderRegex = 
context.getProperty(ATTRIBUTES_AS_HEADERS_REGEX).getValue();
+        attributeHeaderPattern = Pattern.compile(attributeHeaderRegex);
+
+        readerFactory = 
context.getProperty(RECORD_READER).asControllerService(RecordReaderFactory.class);
+        writerFactory = 
context.getProperty(RECORD_WRITER).asControllerService(RecordSetWriterFactory.class);
+    }
+
     /**
      * Will construct JMS {@link Message} by extracting its body from the
      * incoming {@link FlowFile}. {@link FlowFile} attributes that represent
@@ -211,20 +229,21 @@ public class PublishJMS extends 
AbstractJMSProcessor<JMSPublisher> {
      */
     @Override
     protected void rendezvousWithJms(ProcessContext context, ProcessSession 
processSession, JMSPublisher publisher) throws ProcessException {
-        FlowFile flowFile = processSession.get();
-        if (flowFile != null) {
+        final List<FlowFile> flowFiles = 
processSession.get(context.getProperty(MAX_BATCH_SIZE).asInteger());
+        if (flowFiles.isEmpty()) {
+            return;
+        }
+
+        flowFiles.forEach(flowFile -> {
             try {
                 final String destinationName = 
context.getProperty(DESTINATION).evaluateAttributeExpressions(flowFile).getValue();
                 final String charset = 
context.getProperty(CHARSET).evaluateAttributeExpressions(flowFile).getValue();
-                final Boolean allowIllegalChars = 
context.getProperty(ALLOW_ILLEGAL_HEADER_CHARS).asBoolean();
-                final String attributeHeaderRegex = 
context.getProperty(ATTRIBUTES_AS_HEADERS_REGEX).getValue();
 
                 final Map<String, String> attributesToSend = new HashMap<>();
                 // REGEX Attributes
-                final Pattern pattern = Pattern.compile(attributeHeaderRegex);
                 for (final Map.Entry<String, String> entry : 
flowFile.getAttributes().entrySet()) {
                     final String key = entry.getKey();
-                    if (pattern.matcher(key).matches()) {
+                    if (attributeHeaderPattern.matcher(key).matches()) {
                         if (allowIllegalChars || key.endsWith(".type") || 
(!key.contains("-") && !key.contains("."))) {
                             attributesToSend.put(key, 
flowFile.getAttribute(key));
                         }
@@ -232,9 +251,6 @@ public class PublishJMS extends 
AbstractJMSProcessor<JMSPublisher> {
                 }
 
                 if (context.getProperty(RECORD_READER).isSet()) {
-                    final RecordReaderFactory readerFactory = 
context.getProperty(RECORD_READER).asControllerService(RecordReaderFactory.class);
-                    final RecordSetWriterFactory writerFactory = 
context.getProperty(RECORD_WRITER).asControllerService(RecordSetWriterFactory.class);
-
                     final FlowFileReader flowFileReader = new 
StateTrackingFlowFileReader(
                             getIdentifier(),
                             new RecordSupplier(readerFactory, writerFactory),
@@ -278,7 +294,7 @@ public class PublishJMS extends 
AbstractJMSProcessor<JMSPublisher> {
             } catch (Exception e) {
                 handleException(context, processSession, publisher, flowFile, 
e);
             }
-        }
+        });
     }
 
     private void handleException(ProcessContext context, ProcessSession 
processSession, JMSPublisher publisher, FlowFile flowFile, Exception e) {
diff --git 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/ConsumeJMSIT.java
 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/ConsumeJMSIT.java
index cc78def0bc..03032477ce 100644
--- 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/ConsumeJMSIT.java
+++ 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/ConsumeJMSIT.java
@@ -503,6 +503,7 @@ public class ConsumeJMSIT {
             TestRunner testRunner = 
initializeTestRunner(jmsTemplate.getConnectionFactory(), destination);
             testRunner.setProperty(ConsumeJMS.RECORD_READER, 
createJsonRecordSetReaderService(testRunner));
             testRunner.setProperty(ConsumeJMS.RECORD_WRITER, 
createJsonRecordSetWriterService(testRunner));
+            testRunner.setProperty(AbstractJMSProcessor.MAX_BATCH_SIZE, "10");
 
             testRunner.run(1, false);
 
@@ -535,6 +536,7 @@ public class ConsumeJMSIT {
             TestRunner testRunner = 
initializeTestRunner(jmsTemplate.getConnectionFactory(), destination);
             testRunner.setProperty(ConsumeJMS.RECORD_READER, 
createJsonRecordSetReaderService(testRunner));
             testRunner.setProperty(ConsumeJMS.RECORD_WRITER, 
createJsonRecordSetWriterService(testRunner));
+            testRunner.setProperty(AbstractJMSProcessor.MAX_BATCH_SIZE, "10");
             testRunner.setRelationshipAvailable(ConsumeJMS.REL_PARSE_FAILURE);
 
             testRunner.run(1, false);
@@ -568,6 +570,7 @@ public class ConsumeJMSIT {
             TestRunner testRunner = 
initializeTestRunner(jmsTemplate.getConnectionFactory(), destination);
             testRunner.setProperty(ConsumeJMS.RECORD_READER, 
createJsonRecordSetReaderService(testRunner));
             testRunner.setProperty(ConsumeJMS.RECORD_WRITER, 
createJsonRecordSetWriterService(testRunner));
+            testRunner.setProperty(AbstractJMSProcessor.MAX_BATCH_SIZE, "10");
             testRunner.setProperty(ConsumeJMS.OUTPUT_STRATEGY, 
OutputStrategy.USE_APPENDER.getValue());
 
             testRunner.run(1, false);
@@ -609,6 +612,7 @@ public class ConsumeJMSIT {
             TestRunner testRunner = 
initializeTestRunner(jmsTemplate.getConnectionFactory(), destination);
             testRunner.setProperty(ConsumeJMS.RECORD_READER, 
createJsonRecordSetReaderService(testRunner));
             testRunner.setProperty(ConsumeJMS.RECORD_WRITER, 
createJsonRecordSetWriterService(testRunner));
+            testRunner.setProperty(AbstractJMSProcessor.MAX_BATCH_SIZE, "10");
             testRunner.setProperty(ConsumeJMS.OUTPUT_STRATEGY, 
OutputStrategy.USE_WRAPPER.getValue());
 
             testRunner.run(1, false);
diff --git 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/JMSPublisherConsumerIT.java
 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/JMSPublisherConsumerIT.java
index 3aafca5273..d41ba89dc7 100644
--- 
a/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/JMSPublisherConsumerIT.java
+++ 
b/nifi-extension-bundles/nifi-jms-bundle/nifi-jms-processors/src/test/java/org/apache/nifi/jms/processors/JMSPublisherConsumerIT.java
@@ -43,8 +43,10 @@ import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -197,9 +199,9 @@ public class JMSPublisherConsumerIT {
             jmsTemplate.send(destinationName, messageCreator);
 
             JMSConsumer consumer = new JMSConsumer((CachingConnectionFactory) 
jmsTemplate.getConnectionFactory(), jmsTemplate, mock(ComponentLog.class));
-            consumer.consumeSingleMessage(destinationName, null, false, false, 
null, null, "UTF-8", response -> {
+            consumer.consumeMessageSet(destinationName, null, false, false, 
null, null, "UTF-8", 1, responses -> {
                 callbackInvoked.set(true);
-                responseChecker.accept(response);
+                responseChecker.accept(responses.getFirst());
             });
 
             assertTrue(callbackInvoked.get());
@@ -300,6 +302,7 @@ public class JMSPublisherConsumerIT {
             ((CachingConnectionFactory) 
jmsTemplate.getConnectionFactory()).destroy();
         }
     }
+
     /**
      * At the moment the only two supported message types are TextMessage and
      * BytesMessage which is sufficient for the type if JMS use cases NiFi is
@@ -320,7 +323,7 @@ public class JMSPublisherConsumerIT {
             });
 
             JMSConsumer consumer = new JMSConsumer((CachingConnectionFactory) 
jmsTemplate.getConnectionFactory(), jmsTemplate, mock(ComponentLog.class));
-            consumer.consumeSingleMessage(destinationName, null, false, false, 
null, null, "UTF-8", response -> {
+            consumer.consumeMessageSet(destinationName, null, false, false, 
null, null, "UTF-8", 1, responses -> {
                 // noop
             });
         } finally {
@@ -347,8 +350,9 @@ public class JMSPublisherConsumerIT {
 
             JMSConsumer consumer = new JMSConsumer((CachingConnectionFactory) 
jmsTemplate.getConnectionFactory(), jmsTemplate, mock(ComponentLog.class));
             final AtomicBoolean callbackInvoked = new AtomicBoolean();
-            consumer.consumeSingleMessage(destinationName, null, false, false, 
null, null, "UTF-8", response -> {
+            consumer.consumeMessageSet(destinationName, null, false, false, 
null, null, "UTF-8", 1, responses -> {
                 callbackInvoked.set(true);
+                JMSResponse response = responses.getFirst();
                 assertEquals("hello from the other side", new 
String(response.getMessageBody()));
                 assertEquals("fooQueue", 
response.getMessageHeaders().get(JmsHeaders.REPLY_TO));
                 assertEquals("foo", 
response.getMessageProperties().get("foo"));
@@ -361,6 +365,76 @@ public class JMSPublisherConsumerIT {
         }
     }
 
+    @Test
+    @Timeout(value = 20000, unit = TimeUnit.MILLISECONDS)
+    public void testMultipleThreadsWithBatchConsume() throws Exception {
+        final int threadCount = 4;
+        final int totalMessageCount = 1000;
+
+        String destinationName = "testMultipleThreads";
+        JmsTemplate publishTemplate = 
CommonTest.buildJmsTemplateForDestination(false);
+        final CountDownLatch consumerTemplateCloseCount = new 
CountDownLatch(threadCount);
+        final AtomicInteger msgCounter = new AtomicInteger(0);
+        final boolean[] msgConsumed = new boolean[totalMessageCount];
+
+        try {
+            for (int i = 0; i < threadCount; i++) {
+                // Start "threadCount" consumers
+                final Thread t = new Thread(() -> {
+                    JmsTemplate consumeTemplate = 
CommonTest.buildJmsTemplateForDestination(false);
+
+                    try {
+                        JMSConsumer consumer = new 
JMSConsumer((CachingConnectionFactory) consumeTemplate.getConnectionFactory(), 
consumeTemplate, mock(ComponentLog.class));
+
+                        while (msgCounter.get() < totalMessageCount) {
+                            consumer.consumeMessageSet(destinationName, null, 
false, false, null, null, "UTF-8", 5,
+                                    responses -> {
+                                        responses.forEach( response -> {
+                                            msgCounter.incrementAndGet();
+                                            String body = new 
String(response.getMessageBody(), StandardCharsets.UTF_8);
+                                            int msgNum = 0;
+                                            try {
+                                                msgNum = 
Integer.parseInt(body);
+                                            } catch (NumberFormatException e) {
+                                                System.out.println("Bad 
message with unexpected body: " + body);
+                                            }
+                                            msgConsumed[msgNum] = true;
+                                        });
+                                    });
+                        }
+                    } finally {
+                        ((CachingConnectionFactory) 
consumeTemplate.getConnectionFactory()).destroy();
+                        consumerTemplateCloseCount.countDown();
+                    }
+                });
+
+                t.start();
+            }
+
+            // Publish "totalMessageCount" messages
+            JMSPublisher publisher = new 
JMSPublisher((CachingConnectionFactory) publishTemplate.getConnectionFactory(), 
publishTemplate, mock(ComponentLog.class));
+            for (int i = 0; i < totalMessageCount; i++) {
+                publisher.publish(destinationName, 
String.valueOf(i).getBytes(StandardCharsets.UTF_8));
+            }
+
+        } finally {
+            ((CachingConnectionFactory) 
publishTemplate.getConnectionFactory()).destroy();
+
+            consumerTemplateCloseCount.await();
+        }
+
+        // Verify we consumed all messages
+        boolean receivedAllMessages = true;
+        for (int i = 0; i < totalMessageCount; i++) {
+            if (!msgConsumed[i]) {
+                System.out.println("Did not receive message " + i);
+                receivedAllMessages = false;
+                break;
+            }
+        }
+        assertTrue(receivedAllMessages, "Did not receive all messages!");
+    }
+
     @Test
     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
     public void validateMessageRedeliveryWhenNotAcked() {
@@ -374,9 +448,9 @@ public class JMSPublisherConsumerIT {
             JMSConsumer consumer = new JMSConsumer((CachingConnectionFactory) 
jmsTemplate.getConnectionFactory(), jmsTemplate, mock(ComponentLog.class));
             final AtomicBoolean callbackInvoked = new AtomicBoolean();
             try {
-                consumer.consumeSingleMessage(destinationName, null, false, 
false, null, null, "UTF-8", response -> {
+                consumer.consumeMessageSet(destinationName, null, false, 
false, null, null, "UTF-8", 1, responses -> {
                     callbackInvoked.set(true);
-                    assertEquals("1", new String(response.getMessageBody()));
+                    assertEquals("1", new 
String(responses.getFirst().getMessageBody()));
                     throw new RuntimeException("intentional to avoid explicit 
ack");
                 });
             } catch (Exception e) {
@@ -388,11 +462,8 @@ public class JMSPublisherConsumerIT {
 
             // should receive the same message, but will process it 
successfully
             while (!callbackInvoked.get()) {
-                consumer.consumeSingleMessage(destinationName, null, false, 
false, null, null, "UTF-8", response -> {
-                    if (response == null) {
-                        return;
-                    }
-
+                consumer.consumeMessageSet(destinationName, null, false, 
false, null, null, "UTF-8", 1, responses -> {
+                    JMSResponse response = responses.getFirst();
                     callbackInvoked.set(true);
                     assertEquals("2", new String(response.getMessageBody()));
                     acknowledge(response);
@@ -405,13 +476,9 @@ public class JMSPublisherConsumerIT {
             // receiving next message and fail again
             try {
                 while (!callbackInvoked.get()) {
-                    consumer.consumeSingleMessage(destinationName, null, 
false, false, null, null, "UTF-8", response -> {
-                        if (response == null) {
-                            return;
-                        }
-
+                    consumer.consumeMessageSet(destinationName, null, false, 
false, null, null, "UTF-8", 1, responses -> {
                         callbackInvoked.set(true);
-                        assertEquals("1", new 
String(response.getMessageBody()));
+                        assertEquals("1", new 
String(responses.getFirst().getMessageBody()));
                         throw new RuntimeException("intentional to avoid 
explicit ack");
                     });
                 }
@@ -424,11 +491,8 @@ public class JMSPublisherConsumerIT {
             // should receive the same message, but will process it 
successfully
             try {
                 while (!callbackInvoked.get()) {
-                    consumer.consumeSingleMessage(destinationName, null, 
false, false, null, null, "UTF-8", response -> {
-                        if (response == null) {
-                            return;
-                        }
-
+                    consumer.consumeMessageSet(destinationName, null, false, 
false, null, null, "UTF-8", 1, responses -> {
+                        JMSResponse response = responses.getFirst();
                         callbackInvoked.set(true);
                         assertEquals("1", new 
String(response.getMessageBody()));
                         acknowledge(response);
@@ -472,9 +536,9 @@ public class JMSPublisherConsumerIT {
 
             JMSConsumer consumer = new JMSConsumer((CachingConnectionFactory) 
jmsTemplate.getConnectionFactory(), jmsTemplate, mock(ComponentLog.class));
             AtomicBoolean callbackInvoked = new AtomicBoolean();
-            consumer.consumeSingleMessage(destinationName, null, false, false, 
null, messageSelector, "UTF-8", response -> {
+            consumer.consumeMessageSet(destinationName, null, false, false, 
null, messageSelector, "UTF-8", 1, responses -> {
                 callbackInvoked.set(true);
-                assertEquals("msg1", new String(response.getMessageBody()));
+                assertEquals("msg1", new 
String(responses.getFirst().getMessageBody()));
             });
             assertTrue(callbackInvoked.get());
 
diff --git 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java
 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java
index f59320a341..780e37c3ae 100644
--- 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java
+++ 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java
@@ -182,7 +182,7 @@ public class GetFile extends AbstractProcessor {
             .build();
     public static final PropertyDescriptor BATCH_SIZE = new 
PropertyDescriptor.Builder()
             .name("Batch Size")
-            .description("The maximum number of files to pull in each 
iteration")
+            .description("The maximum number of files to pull in each 
invocation of the processor")
             .required(true)
             .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
             .defaultValue("10")


Reply via email to