Skip to content

GH-10083: Apply Nullability to spring-integration-mail #10096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import jakarta.mail.Store;
import jakarta.mail.URLName;
import jakarta.mail.internet.MimeMessage;
import org.jspecify.annotations.Nullable;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.expression.Expression;
Expand Down Expand Up @@ -79,33 +80,34 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
*/
public static final String DEFAULT_SI_USER_FLAG = "spring-integration-mail-adapter";

private final URLName url;
private final @Nullable URLName url;

private final ReentrantReadWriteLock folderLock = new ReentrantReadWriteLock();

private final Lock folderReadLock = this.folderLock.readLock();

private final Lock folderWriteLock = this.folderLock.writeLock();

private String protocol;
private @Nullable String protocol;

private int maxFetchSize = -1;

private Session session;
private @Nullable Session session;

private boolean shouldDeleteMessages;

private int folderOpenMode = Folder.READ_ONLY;

private Properties javaMailProperties = new Properties();

private Authenticator javaMailAuthenticator;
private @Nullable Authenticator javaMailAuthenticator;

@SuppressWarnings("NullAway.Init")
private StandardEvaluationContext evaluationContext;

private Expression selectorExpression;
private @Nullable Expression selectorExpression;

private HeaderMapper<MimeMessage> headerMapper;
private @Nullable HeaderMapper<MimeMessage> headerMapper;

private String userFlag = DEFAULT_SI_USER_FLAG;

Expand All @@ -117,9 +119,9 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl

private boolean flaggedAsFallback = true;

private volatile Store store;
private volatile @Nullable Store store;

private volatile Folder folder;
private volatile @Nullable Folder folder;

public AbstractMailReceiver() {
this.url = null;
Expand All @@ -130,7 +132,7 @@ public AbstractMailReceiver(URLName urlName) {
this.url = urlName;
}

public AbstractMailReceiver(String url) {
public AbstractMailReceiver(@Nullable String url) {
if (url != null) {
this.url = new URLName(url);
}
Expand All @@ -139,7 +141,7 @@ public AbstractMailReceiver(String url) {
}
}

public void setSelectorExpression(Expression selectorExpression) {
public void setSelectorExpression(@Nullable Expression selectorExpression) {
this.selectorExpression = selectorExpression;
}

Expand Down Expand Up @@ -306,7 +308,7 @@ public void setFlaggedAsFallback(boolean flaggedAsFallback) {
this.flaggedAsFallback = flaggedAsFallback;
}

protected Folder getFolder() {
protected @Nullable Folder getFolder() {
return this.folder;
}

Expand Down Expand Up @@ -334,6 +336,7 @@ private void openSession() {

private void connectStoreIfNecessary() throws MessagingException {
if (this.store == null) {
Assert.state(this.session != null, "'session' should not be null");
if (this.url != null) {
this.store = this.session.getStore(this.url);
}
Expand All @@ -345,7 +348,8 @@ else if (this.protocol != null) {
}
}
if (!this.store.isConnected()) {
this.logger.debug(() -> "connecting to store [" + this.store.getURLName() + "]");
URLName urlName = this.store.getURLName();
this.logger.debug(() -> "connecting to store [" + urlName + "]");
this.store.connect();
}
}
Expand All @@ -360,7 +364,8 @@ protected void openFolder() throws MessagingException {
connectStoreIfNecessary();
}
if (this.folder == null || !this.folder.exists()) {
throw new IllegalStateException("no such folder [" + this.url.getFile() + "]");
String file = this.url != null ? this.url.getFile() : "";
throw new IllegalStateException("no such folder [" + file + "]");
}
if (this.folder.isOpen()) {
return;
Expand All @@ -371,6 +376,7 @@ protected void openFolder() throws MessagingException {
}

private Folder obtainFolderInstance() throws MessagingException {
Assert.state(this.store != null, "'store' should not be null");
if (this.url == null) {
return this.store.getDefaultFolder();
}
Expand Down Expand Up @@ -422,7 +428,9 @@ protected void closeFolder() {
}

private MimeMessage[] searchAndFilterMessages() throws MessagingException {
this.logger.debug(() -> "attempting to receive mail from folder [" + this.folder.getFullName() + "]");
Assert.state(this.folder != null, "'folder' should not be null");
String fullName = this.folder.getFullName();
this.logger.debug(() -> "attempting to receive mail from folder [" + fullName + "]");
Message[] messagesToProcess;
Message[] messages = searchForNewMessages();
if (this.maxFetchSize > 0 && messages.length > this.maxFetchSize) {
Expand Down Expand Up @@ -549,7 +557,9 @@ private void setMessageFlagsAndMaybeDeleteMessages(Message[] messages) throws Me
private void setMessageFlags(Message[] filteredMessages) throws MessagingException {
boolean recentFlagSupported = false;

Flags flags = getFolder().getPermanentFlags();
Folder folder = getFolder();
Assert.state(folder != null, "'folder' should not be null");
Flags flags = folder.getPermanentFlags();

if (flags != null) {
recentFlagSupported = flags.contains(Flags.Flag.RECENT);
Expand Down Expand Up @@ -612,6 +622,7 @@ protected void fetchMessages(Message[] messages) throws MessagingException {
contentsProfile.add(FetchProfile.Item.ENVELOPE);
contentsProfile.add(FetchProfile.Item.CONTENT_INFO);
contentsProfile.add(FetchProfile.Item.FLAGS);
Assert.state(this.folder != null, "'folder' should not be null");
this.folder.fetch(messages, contentsProfile);
}

Expand Down Expand Up @@ -658,10 +669,12 @@ protected void onInit() {

@Override
public String toString() {
return this.url.toString();
String urlName = this.store != null && this.store.getURLName() != null
? this.store.getURLName().toString() : "";
return this.url != null ? this.url.toString() : urlName;
}

Store getStore() {
@Nullable Store getStore() {
return this.store;
}

Expand All @@ -678,7 +691,7 @@ private final class IntegrationMimeMessage extends MimeMessage {

private final MimeMessage source;

private final Object content;
private final @Nullable Object content;

IntegrationMimeMessage(MimeMessage source) throws MessagingException {
super(source);
Expand All @@ -700,7 +713,7 @@ private final class IntegrationMimeMessage extends MimeMessage {
}

@Override
public Folder getFolder() {
public @Nullable Folder getFolder() {
if (!AbstractMailReceiver.this.autoCloseFolder) {
return AbstractMailReceiver.this.folder;
}
Expand Down Expand Up @@ -731,7 +744,7 @@ public int getLineCount() throws MessagingException {
}

@Override
public Object getContent() throws IOException, MessagingException {
public @Nullable Object getContent() throws IOException, MessagingException {
if (AbstractMailReceiver.this.simpleContent) {
return super.getContent();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,6 +24,7 @@
import jakarta.mail.Folder;
import jakarta.mail.Message;
import org.aopalliance.aop.Advice;
import org.jspecify.annotations.Nullable;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
Expand All @@ -35,7 +36,6 @@
import org.springframework.integration.transaction.IntegrationResourceHolder;
import org.springframework.integration.transaction.IntegrationResourceHolderSynchronization;
import org.springframework.integration.transaction.TransactionSynchronizationFactory;
import org.springframework.lang.Nullable;
import org.springframework.messaging.MessagingException;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
Expand All @@ -62,18 +62,22 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport implements Be

private final ImapMailReceiver mailReceiver;

@SuppressWarnings("NullAway.Init")
private Executor taskExecutor;

private TransactionSynchronizationFactory transactionSynchronizationFactory;
private @Nullable TransactionSynchronizationFactory transactionSynchronizationFactory;

@SuppressWarnings("NullAway.Init")
private ClassLoader classLoader;

@SuppressWarnings("NullAway.Init")
private ApplicationEventPublisher applicationEventPublisher;

private boolean shouldReconnectAutomatically = true;

private List<Advice> adviceChain;
private @Nullable List<Advice> adviceChain;

@SuppressWarnings("NullAway.Init")
private Consumer<Object> messageSender;

private long reconnectDelay = DEFAULT_RECONNECT_DELAY; // milliseconds
Expand Down Expand Up @@ -252,8 +256,8 @@ private void delayNextIdleCall() {
}
}

@Nullable
private static jakarta.mail.MessagingException getJakartaMailMessagingExceptionFromCause(Throwable cause) {
private static jakarta.mail.@Nullable MessagingException getJakartaMailMessagingExceptionFromCause(
@Nullable Throwable cause) {
if (cause == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import jakarta.mail.search.NotTerm;
import jakarta.mail.search.SearchTerm;
import org.eclipse.angus.mail.imap.IMAPFolder;
import org.jspecify.annotations.Nullable;

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
Expand Down Expand Up @@ -67,19 +68,20 @@ public class ImapMailReceiver extends AbstractMailReceiver {

private long cancelIdleInterval = DEFAULT_CANCEL_IDLE_INTERVAL;

@SuppressWarnings("NullAway.Init")
private TaskScheduler scheduler;

private boolean isInternalScheduler;

private volatile ScheduledFuture<?> pingTask;
private volatile @Nullable ScheduledFuture<?> pingTask;

@SuppressWarnings("this-escape")
public ImapMailReceiver() {
setProtocol(PROTOCOL);
}

@SuppressWarnings("this-escape")
public ImapMailReceiver(String url) {
public ImapMailReceiver(@Nullable String url) {
super(url);
if (url != null) {
Assert.isTrue(url.toLowerCase(Locale.ROOT).startsWith(PROTOCOL),
Expand Down Expand Up @@ -212,6 +214,7 @@ else if (!folder.getPermanentFlags().contains(Flags.Flag.RECENT) && searchForNew
@Override
protected Message[] searchForNewMessages() throws MessagingException {
Folder folderToUse = getFolder();
Assert.state(folderToUse != null, "'folderToUse' should not be null");
Flags supportedFlags = folderToUse.getPermanentFlags();
SearchTerm searchTerm = compileSearchTerms(supportedFlags);
if (folderToUse.isOpen()) {
Expand All @@ -238,8 +241,10 @@ private Message[] nullSafeMessages(Message[] messageArray) {
}
}

private SearchTerm compileSearchTerms(Flags supportedFlags) {
return this.searchTermStrategy.generateSearchTerm(supportedFlags, this.getFolder());
private @Nullable SearchTerm compileSearchTerms(Flags supportedFlags) {
Folder folderToUse = this.getFolder();
Assert.state(folderToUse != null, "'folderToUse' should not be null");
return this.searchTermStrategy.generateSearchTerm(supportedFlags, folderToUse);
}

@Override
Expand Down Expand Up @@ -277,7 +282,7 @@ private class DefaultSearchTermStrategy implements SearchTermStrategy {
}

@Override
public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
public @Nullable SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
SearchTerm searchTerm = null;
boolean recentFlagSupported = false;
if (supportedFlags != null) {
Expand Down Expand Up @@ -320,7 +325,7 @@ public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
return searchTerm;
}

private SearchTerm applyTermsWhenNoRecentFlag(Folder folder, SearchTerm searchTerm) {
private SearchTerm applyTermsWhenNoRecentFlag(Folder folder, @Nullable SearchTerm searchTerm) {
NotTerm notFlagged;
if (folder.getPermanentFlags().contains(Flag.USER)) {
logger.debug(() -> "This email server does not support RECENT flag, but it does support " +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,8 @@

package org.springframework.integration.mail;

import org.jspecify.annotations.Nullable;

/**
* Strategy interface for receiving mail {@link jakarta.mail.Message Messages}.
*
Expand All @@ -25,6 +27,6 @@
*/
public interface MailReceiver {

Object[] receive() throws jakarta.mail.MessagingException;
Object @Nullable [] receive() throws jakarta.mail.MessagingException;

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,8 @@
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.jspecify.annotations.Nullable;

import org.springframework.core.log.LogMessage;
import org.springframework.integration.endpoint.AbstractMessageSource;
import org.springframework.messaging.MessagingException;
Expand Down Expand Up @@ -54,7 +56,7 @@ public String getComponentType() {
}

@Override
protected Object doReceive() {
protected @Nullable Object doReceive() {
try {
Object mailMessage = this.mailQueue.poll();
if (mailMessage == null) {
Expand Down
Loading