1

I have a Spring Boot project that is leveraging Spring Integration. My goal is to to poll a POP3 mail server regularly, and download any attachments associated with those messages. My relevant Spring Config looks like this:

@Configuration
public class MailIntegrationConfig {

    @Value("${path.output.temp}")
    private String outPath;

    @Bean
    public MessageChannel mailChannel() {
        return new DirectChannel();
    }

    @Bean
    @InboundChannelAdapter(value = "mailChannel", poller = @Poller(fixedDelay = "16000"))
    public MessageSource<Object> fileReadingMessageSource() {
        var receiver = new Pop3MailReceiver("pop3s://user:passwordexample.com/INBOX");
        var mailProperties = new Properties();
        mailProperties.setProperty("mail.pop3.port", "995");
        mailProperties.put("mail.pop3.ssl.enable", true);
        receiver.setShouldDeleteMessages(false);
        receiver.setMaxFetchSize(10);
        receiver.setJavaMailProperties(mailProperties);
        // receiver.setHeaderMapper(new DefaultMailHeaderMapper());
        var source = new MailReceivingMessageSource(receiver);

        return source;

    }

    @Bean
    @ServiceActivator(inputChannel = "mailChannel")
    public MessageHandler popMessageHandler() {
        return new MailReceivingMessageHandler(outPath);
    }

}

My MailReceivingMessageHandler class (partial)

public class MailReceivingMessageHandler extends AbstractMessageHandler {

    private String outDir;

    public MailReceivingMessageHandler(String outDir) {
        var outPath = new File(outDir);
        if (!outPath.exists()) {
            throw new IllegalArgumentException(String.format("%s does not exist.", outDir));
        }
        this.outDir = outDir;
    }

    @Override
    protected void handleMessageInternal(org.springframework.messaging.Message<?> message) {
        Object payload = message.getPayload();
        if (!(payload instanceof Message)) {

            throw new IllegalArgumentException(
                    "Unable to create MailMessage from payload type [" + message.getPayload().getClass().getName()
                            + "], " + "expected MimeMessage, MailMessage, byte array or String.");
        }
        try {
            var msg = (Message) payload;
            System.out.println(String.format("Headers [%s] Subject [%s]. Content-Type [%s].", msg.getAllHeaders(),
                    msg.getSubject(), msg.getContentType()));
            this.handleMessage(msg);

        } catch (IOException | MessagingException e) {
            e.printStackTrace();
        }

    }

    private void handleMessage(Message msg) throws MessagingException, IOException {
        var cType = msg.getContentType();

        if (cType.contains(MediaType.TEXT_PLAIN_VALUE)) {
            handleText((String) msg.getContent());
        } else if (cType.contains(MediaType.MULTIPART_MIXED_VALUE)) {
            handleMultipart((Multipart) msg.getContent());
        }

    }


    // See
    // https://stackoverflow.com/questions/1748183/download-attachments-using-java-mail
    private void handleMultipart(Multipart msgContent) throws MessagingException, IOException {
        var mCount = msgContent.getCount();
        for (var i = 0; i < mCount; i++) {
            this.processAttachments(msgContent.getBodyPart(i));

        }

    }

    private void processAttachments(BodyPart part) throws IOException, MessagingException {
        var content = part.getContent();

        if (content instanceof InputStream || content instanceof String) {
            if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) || !part.getFileName().isBlank()) {
                var fName = String.format("%s.%s", UUID.randomUUID().toString(),
                        FilenameUtils.getExtension(part.getFileName()));
                FileUtils.copyInputStreamToFile(part.getInputStream(), new File(outDir + File.separator + fName));
            }

            if (content instanceof Multipart) {
                Multipart multipart = (Multipart) content;
                for (int i = 0; i < multipart.getCount(); i++) {
                    var bodyPart = multipart.getBodyPart(i);
                    processAttachments(bodyPart);
                }
            }
        }

    }
}

Whenever I run my code using the config above, I receive the following error:

javax.mail.MessagingException: No inputstream from datasource;
  nested exception is:
    java.lang.IllegalStateException: Folder is not Open
    at javax.mail.internet.MimeMultipart.parse(MimeMultipart.java:576)
    at javax.mail.internet.MimeMultipart.getCount(MimeMultipart.java:312)
    at com.midamcorp.data.mail.MailReceivingMessageHandler.handleMultipart(MailReceivingMessageHandler.java:70)
    at com.midamcorp.data.mail.MailReceivingMessageHandler.handleMessage(MailReceivingMessageHandler.java:58)
    at com.midamcorp.data.mail.MailReceivingMessageHandler.handleMessageInternal(MailReceivingMessageHandler.java:44)
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:62)
    at org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper.handleRequestMessage(ReplyProducingMessageHandlerWrapper.java:58)
    at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:134)
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:62)
    at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:115)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:133)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106)
    at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:72)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:570)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:520)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:187)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:166)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47)
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:109)
    at org.springframework.integration.endpoint.SourcePollingChannelAdapter.handleMessage(SourcePollingChannelAdapter.java:196)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.messageReceived(AbstractPollingEndpoint.java:444)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:428)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.pollForMessage(AbstractPollingEndpoint.java:376)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.lambda$null$3(AbstractPollingEndpoint.java:323)
    at org.springframework.integration.util.ErrorHandlingTaskExecutor.lambda$execute$0(ErrorHandlingTaskExecutor.java:57)
    at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
    at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:55)
    at org.springframework.integration.endpoint.AbstractPollingEndpoint.lambda$createPoller$4(AbstractPollingEndpoint.java:320)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.IllegalStateException: Folder is not Open
    at com.sun.mail.pop3.POP3Folder.checkOpen(POP3Folder.java:562)
    at com.sun.mail.pop3.POP3Folder.getProtocol(POP3Folder.java:592)
    at com.sun.mail.pop3.POP3Message.getRawStream(POP3Message.java:154)
    at com.sun.mail.pop3.POP3Message.getContentStream(POP3Message.java:251)
    at javax.mail.internet.MimePartDataSource.getInputStream(MimePartDataSource.java:78)
    at javax.mail.internet.MimeMultipart.parse(MimeMultipart.java:570)
    ... 35 more

Obviously, the root cause is clear - the POP3 folder is closed. I have seen solutions that would likely be able to handle when just the Java mail classes are used, but none with Spring Integration. My question is how does one properly control when a folder is open or closed using Spring Integration Mail? I realize the Pop3MailReceiver class has a .setAutoCloseFolder() method. Based on the Spring Docs, I assume I need to set that, along something like the following to my handler:

        Closeable closeableResource = StaticMessageHeaderAccessor.getCloseableResource(message);
        if (closeableResource != null) {
            closeableResource.close();
        }

However, if I set autoCloseFolder to false, it does not appear as if the message even ever "hits" my handler, so unfortunately being able to close the resource does not even matter at this point. That is, when autoClose is set to false, the 'handleMessageInternal()' method in my handler class is never reached even though there are indeed message on the POP3 server. Instead I just get a bunch of logs like this:

2020-06-26 15:26:54.523  INFO 15348 --- [   scheduling-1] o.s.integration.mail.Pop3MailReceiver    : attempting to receive mail from folder [INBOX] 

What am I missing? Thanks.

KellyM
  • 2,472
  • 6
  • 46
  • 90
  • 1
    You definitely need to use autoClose as false. Also how does it work if you make a `Pop3MailReceiver` as a bean?.. – Artem Bilan Jun 26 '20 at 21:05
  • @ArtemBilan, thanks much for the response. I believe that may have part of the issue. After more debugging, I figured out the issue was related to the case when the mailbox was empty *at any point* when the program was running, even if mail later came later. When this happened, the folder remained perpetually open as the message handler and the code that would have closed the folder was not reached. My solution was to extend `Pop3MailReceiver` and override the `searchForNewMessages` method. – KellyM Jun 30 '20 at 12:54
  • Sounds like we need introduce something like `closeFolderOnIdlePeriod`. Or this is really POP3 specific that we have to reopen folder after fetching existing messages – Artem Bilan Jun 30 '20 at 13:08
  • @ArtemBilan : Pls guide me if we have a fix available for this issue in the latest spring integration versions, I'm not able to find any references in spring docs. This is easily reproducible with email inbound adapter configured to use POP3. For emails without attachment it works fine but with attachment it gives the same error mentioned in this post and to resolve this we can set autoClose to "false" but then adapter stops reading the new messages ( after a gap of few minutes) as mentioned in this post. pls suggest. – Abdul Mohsin Feb 03 '22 at 04:48

0 Answers0