This is an automated email from the ASF dual-hosted git repository.
pvillard 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 1d2c25fc74c NIFI-15795 Fixed Email Processors Executor Service
handling (#11105)
1d2c25fc74c is described below
commit 1d2c25fc74cfd798d08a8cf042ec6be01ec2f813
Author: David Handermann <[email protected]>
AuthorDate: Fri Apr 3 16:45:39 2026 -0500
NIFI-15795 Fixed Email Processors Executor Service handling (#11105)
- Relocated Scheduled Executor Service creation to instance level with
creation and shutdown in Processor lifecycle methods
- Replaced SLF4J Logger references with standard Component Log
---
.../processors/email/AbstractEmailProcessor.java | 144 +++++++++++----------
1 file changed, 76 insertions(+), 68 deletions(-)
diff --git
a/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/src/main/java/org/apache/nifi/processors/email/AbstractEmailProcessor.java
b/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/src/main/java/org/apache/nifi/processors/email/AbstractEmailProcessor.java
index 9c309734ab7..603a9050cba 100644
---
a/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/src/main/java/org/apache/nifi/processors/email/AbstractEmailProcessor.java
+++
b/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/src/main/java/org/apache/nifi/processors/email/AbstractEmailProcessor.java
@@ -36,8 +36,6 @@ import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -51,13 +49,13 @@ import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map.Entry;
-import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
@@ -181,7 +179,7 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
REL_SUCCESS
);
- protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private ScheduledExecutorService scheduledExecutorService;
protected volatile T messageReceiver;
@@ -191,8 +189,8 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
private volatile ProcessSession processSession;
- protected volatile Optional<OAuth2AccessTokenProvider>
oauth2AccessTokenProviderOptional;
- protected volatile AccessToken oauth2AccessDetails;
+ private volatile OAuth2AccessTokenProvider accessTokenProvider;
+ private volatile AccessToken currentAccessToken;
protected static List<PropertyDescriptor> getCommonPropertyDescriptors() {
return PROPERTY_DESCRIPTORS;
@@ -200,25 +198,35 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
@OnScheduled
public void onScheduled(final ProcessContext context) {
+ final ThreadFactory threadFactory = Thread.ofPlatform()
+
.name("%s[%s]-MailReceiver".formatted(getClass().getSimpleName(),
getIdentifier()))
+ .factory();
+ scheduledExecutorService =
Executors.newSingleThreadScheduledExecutor(threadFactory);
+
if
(context.getProperty(AUTHORIZATION_MODE).getValue().equals(OAUTH_AUTHORIZATION_MODE.getValue()))
{
OAuth2AccessTokenProvider oauth2AccessTokenProvider =
context.getProperty(OAUTH2_ACCESS_TOKEN_PROVIDER).asControllerService(OAuth2AccessTokenProvider.class);
- oauth2AccessTokenProviderOptional =
Optional.of(oauth2AccessTokenProvider);
- oauth2AccessDetails = oauth2AccessTokenProvider.getAccessDetails();
+ accessTokenProvider = oauth2AccessTokenProvider;
+ currentAccessToken = oauth2AccessTokenProvider.getAccessDetails();
} else {
- oauth2AccessTokenProviderOptional = Optional.empty();
- oauth2AccessDetails = null;
+ accessTokenProvider = null;
+ currentAccessToken = null;
}
}
@OnStopped
- public void stop(ProcessContext processContext) {
- this.flushRemainingMessages(processContext);
+ public void onStopped() {
+ flushRemainingMessages();
try {
- this.messageReceiver.destroy();
- this.messageReceiver = null;
- } catch (Exception e) {
- this.logger.warn("Failure while closing processor", e);
+ messageReceiver.destroy();
+ messageReceiver = null;
+ } catch (final Exception e) {
+ getLogger().warn("Failed to close Mail Receiver", e);
+ }
+
+ if (scheduledExecutorService != null) {
+ scheduledExecutorService.shutdownNow();
+ scheduledExecutorService = null;
}
}
@@ -228,12 +236,12 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
}
@Override
- public void onTrigger(ProcessContext context, ProcessSession
processSession) throws ProcessException {
- this.initializeIfNecessary(context, processSession);
+ public void onTrigger(final ProcessContext context, final ProcessSession
session) {
+ initializeIfNecessary(context, session);
- Message emailMessage = this.receiveMessage();
+ final Message emailMessage = receiveMessage();
if (emailMessage != null) {
- this.transfer(emailMessage, context, processSession);
+ transfer(emailMessage, session);
}
}
@@ -273,17 +281,18 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
*/
protected abstract String getProtocol(ProcessContext processContext);
- /**
- * Builds the url used to connect to the email server.
- */
- String buildUrl(ProcessContext processContext) {
+ String buildUrl(final ProcessContext processContext) {
String host =
processContext.getProperty(HOST).evaluateAttributeExpressions().getValue();
String port =
processContext.getProperty(PORT).evaluateAttributeExpressions().getValue();
String user =
processContext.getProperty(USER).evaluateAttributeExpressions().getValue();
- String password =
oauth2AccessTokenProviderOptional.map(oauth2AccessTokenProvider ->
- oauth2AccessTokenProvider.getAccessDetails().getAccessToken()
-
).orElse(processContext.getProperty(PASSWORD).evaluateAttributeExpressions().getValue());
+ final String password;
+ if (accessTokenProvider == null) {
+ password =
processContext.getProperty(PASSWORD).evaluateAttributeExpressions().getValue();
+ } else {
+ final AccessToken accessDetails =
accessTokenProvider.getAccessDetails();
+ password = accessDetails.getAccessToken();
+ }
String folder =
processContext.getProperty(FOLDER).evaluateAttributeExpressions().getValue();
@@ -307,7 +316,7 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
int passwordEndIndex = urlBuilder.indexOf("@");
urlBuilder.replace(passwordStartIndex, passwordEndIndex, "[password]");
this.displayUrl = protocol + "://" + urlBuilder;
- this.logger.info("Connecting to server [{}]", this.displayUrl);
+ getLogger().info("Connecting to server [{}]", this.displayUrl);
return finalUrl;
}
@@ -318,7 +327,7 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
* and is ready to receive messages.
*/
private synchronized void initializeIfNecessary(ProcessContext context,
ProcessSession processSession) {
- if (this.messageReceiver == null || isOauth2AccessDetailsRefreshed()) {
+ if (this.messageReceiver == null || isAccessTokenRefreshRequired()) {
this.processSession = processSession;
this.messageReceiver = this.buildMessageReceiver(context);
@@ -329,10 +338,9 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
// Spring Integration 7 expects an evaluation context bean;
register a lightweight one for the receiver
final StaticListableBeanFactory beanFactory = new
StaticListableBeanFactory();
final StandardEvaluationContext evaluationContext = new
StandardEvaluationContext();
- final ScheduledExecutorService scheduledExecutor =
Executors.newSingleThreadScheduledExecutor();
evaluationContext.setBeanResolver(new
BeanFactoryResolver(beanFactory));
beanFactory.addBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME,
evaluationContext);
-
beanFactory.addBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, new
ConcurrentTaskScheduler(scheduledExecutor));
+
beanFactory.addBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, new
ConcurrentTaskScheduler(scheduledExecutorService));
this.messageReceiver.setBeanFactory(beanFactory);
this.messageReceiver.afterPropertiesSet();
@@ -340,12 +348,16 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
}
}
- private boolean isOauth2AccessDetailsRefreshed() {
- boolean oauthDetailsRefreshed =
this.oauth2AccessTokenProviderOptional.isPresent()
- &&
- (this.oauth2AccessDetails == null ||
!oauth2AccessDetails.equals(this.oauth2AccessTokenProviderOptional.get().getAccessDetails()));
+ private boolean isAccessTokenRefreshRequired() {
+ final boolean refreshRequired;
+
+ if (accessTokenProvider == null) {
+ refreshRequired = false;
+ } else {
+ refreshRequired = currentAccessToken == null ||
!currentAccessToken.equals(accessTokenProvider.getAccessDetails());
+ }
- return oauthDetailsRefreshed;
+ return refreshRequired;
}
/**
@@ -369,7 +381,10 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
final String timeoutInMillis =
String.valueOf(context.getProperty(CONNECTION_TIMEOUT).evaluateAttributeExpressions().asTimePeriod(TimeUnit.MILLISECONDS));
javaMailProperties.setProperty(propertyName, timeoutInMillis);
- oauth2AccessTokenProviderOptional.ifPresent(oauth2AccessTokenProvider
-> javaMailProperties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2"));
+ if (accessTokenProvider != null) {
+ final String authMechanismsProperty =
"mail.%s.auth.mechanisms".formatted(protocol);
+ javaMailProperties.put(authMechanismsProperty, "XOAUTH2");
+ }
return javaMailProperties;
}
@@ -415,37 +430,33 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
return closedException;
}
- /**
- * Disposes the message by converting it to a {@link FlowFile} transferring
- * it to the REL_SUCCESS relationship.
- */
- private void transfer(Message emailMessage, ProcessContext context,
ProcessSession processSession) {
- long start = System.nanoTime();
- FlowFile flowFile = processSession.create();
+ private void transfer(final Message message, final ProcessSession session)
{
+ final long started = System.nanoTime();
+ FlowFile flowFile = session.create();
- flowFile = processSession.append(flowFile, out -> {
+ flowFile = session.write(flowFile, out -> {
try {
- emailMessage.writeTo(out);
- } catch (MessagingException e) {
- throw new IOException(e);
+ message.writeTo(out);
+ } catch (final MessagingException e) {
+ throw new IOException("Message [%d] serialization
failed".formatted(message.getMessageNumber()), e);
}
});
- long executionDuration =
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
+ final long executionDuration =
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - started);
- String fromAddressesString = "";
+ String fromAddresses = "";
try {
- Address[] fromAddresses = emailMessage.getFrom();
- if (fromAddresses != null) {
- fromAddressesString = Arrays.asList(fromAddresses).toString();
+ final Address[] from = message.getFrom();
+ if (from != null) {
+ fromAddresses = Arrays.asList(from).toString();
}
- } catch (MessagingException e) {
- this.logger.warn("Failed to retrieve 'From' attribute from
Message.");
+ } catch (final MessagingException e) {
+ getLogger().warn("Failed to retrieve [From] address from Message
[{}]", message.getMessageNumber());
}
- processSession.getProvenanceReporter().receive(flowFile,
this.displayUrl, "Received message from " + fromAddressesString,
executionDuration);
- this.getLogger().info("Successfully received {} from {} in {} millis",
flowFile, fromAddressesString, executionDuration);
- processSession.transfer(flowFile, REL_SUCCESS);
+ session.getProvenanceReporter().receive(flowFile, displayUrl,
"Received message from " + fromAddresses, executionDuration);
+ getLogger().info("Received {} from {} in {} millis", flowFile,
fromAddresses, executionDuration);
+ session.transfer(flowFile, REL_SUCCESS);
}
@@ -458,26 +469,23 @@ abstract class AbstractEmailProcessor<T extends
AbstractMailReceiver> extends Ab
try {
this.fillMessageQueueIfNecessary();
emailMessage = this.messageQueue.poll(1, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
+ } catch (final InterruptedException e) {
Thread.currentThread().interrupt();
- this.logger.debug("Current thread is interrupted");
+ getLogger().debug("Interrupted while receiving messages");
}
return emailMessage;
}
- /**
- * Will flush the remaining messages when this processor is stopped.
- */
- private void flushRemainingMessages(ProcessContext processContext) {
+ private void flushRemainingMessages() {
Message emailMessage;
try {
while ((emailMessage = this.messageQueue.poll(1,
TimeUnit.MILLISECONDS)) != null) {
- this.transfer(emailMessage, processContext,
this.processSession);
- this.processSession.commitAsync();
+ transfer(emailMessage, processSession);
+ processSession.commitAsync();
}
- } catch (InterruptedException e) {
+ } catch (final InterruptedException e) {
Thread.currentThread().interrupt();
- this.logger.debug("Current thread is interrupted");
+ getLogger().debug("Interrupted while processing remaining
messages");
}
}
}