org.frankframework.filesystem.ImapFileSystem Maven / Gradle / Ivy
/*
Copyright 2020-2024 WeAreFrank!
Licensed 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.frankframework.filesystem;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jakarta.annotation.Nullable;
import jakarta.mail.BodyPart;
import jakarta.mail.Flags;
import jakarta.mail.Folder;
import jakarta.mail.Header;
import jakarta.mail.Message;
import jakarta.mail.Message.RecipientType;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.Part;
import jakarta.mail.Session;
import jakarta.mail.Store;
import jakarta.mail.Transport;
import jakarta.mail.UIDFolder;
import jakarta.mail.URLName;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.angus.mail.imap.AppendUID;
import org.eclipse.angus.mail.imap.IMAPFolder;
import org.eclipse.angus.mail.imap.IMAPMessage;
import org.frankframework.configuration.ConfigurationException;
import org.frankframework.http.PartMessage;
import org.frankframework.util.CredentialFactory;
import org.frankframework.util.StringUtil;
import org.frankframework.xml.SaxElementBuilder;
import org.xml.sax.SAXException;
public class ImapFileSystem extends MailFileSystemBase {
private final @Getter String domain = "IMAP";
private @Getter String host;
private @Getter int port = 993;
private final Session emailSession = Session.getInstance(System.getProperties());
@Override
public void configure() throws ConfigurationException {
if (StringUtils.isEmpty(getHost())) {
throw new ConfigurationException("attribute host needs to be specified");
}
}
@Override
protected IMAPFolder createConnection() throws FileSystemException {
try {
// emailSession.setDebug(true);
CredentialFactory cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword());
Store store = emailSession.getStore("imaps");
try {
store.connect(getHost(), getPort(), cf.getUsername(), cf.getPassword());
} catch (Exception e) {
throw new FileSystemException("Cannot connect to Store at host ["+getHost()+"] port ["+getPort()+"] user ["+cf.getUsername()+"]", e);
}
if (!store.isConnected()) {
throw new FileSystemException("Cannot connect to Store at host ["+getHost()+"] port ["+getPort()+"] user ["+cf.getUsername()+"]");
}
IMAPFolder inbox = (IMAPFolder)store.getFolder("INBOX");
IMAPFolder folder;
String baseFolder = getBaseFolder();
if (StringUtils.isNotEmpty(baseFolder)) {
folder = (IMAPFolder)inbox.getFolder(baseFolder);
if (!folder.exists()) {
folder = (IMAPFolder)store.getFolder(baseFolder);
}
if (!folder.exists()) {
throw new FolderNotFoundException("Could not find baseFolder ["+baseFolder+"]");
}
} else {
folder = inbox;
if (!folder.exists()) {
throw new FolderNotFoundException("Could not find baseFolder ["+folder+"]");
}
}
return folder;
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
protected void closeConnection(IMAPFolder folder) throws FileSystemException {
try (Store store = folder.getStore()) {
if (folder.isOpen()) {
folder.close();
}
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
private IMAPFolder getFolder(IMAPFolder baseFolder, String name) throws MessagingException {
if (StringUtils.isNotEmpty(name)) {
return (IMAPFolder)baseFolder.getFolder(name);
}
return baseFolder;
}
private String uidToName(long uid) {
return Long.toHexString(uid);
}
private long nameToUid(String filename) {
return Long.parseLong(filename, 16);
}
@Override
public String getName(Message f) {
UIDFolder folder = (UIDFolder) f.getFolder();
try {
return uidToName(folder.getUID(f));
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
@Override
public String getParentFolder(Message f) {
return f.getFolder().getFullName();
}
@Override
public Message toFile(@Nullable String filename) throws FileSystemException {
return toFile(null, filename);
}
@Override
public Message toFile(@Nullable String defaultFolder, @Nullable String filename) throws FileSystemException {
IMAPFolder baseFolder = getConnection();
boolean invalidateConnectionOnRelease = false;
try {
IMAPFolder folder = getFolder(baseFolder, defaultFolder);
if (!folder.isOpen()) {
folder.open(Folder.READ_WRITE);
}
return folder.getMessageByUID(nameToUid(filename));
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public boolean exists(Message f) throws FileSystemException {
try {
Folder folder = f.getFolder();
folder.expunge();
return !f.isExpunged() && !f.isSet(Flags.Flag.DELETED);
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public boolean isFolder(Message message) throws FileSystemException {
return false; // Currently only supports messages
}
@Override
public boolean folderExists(String foldername) throws FileSystemException {
IMAPFolder baseFolder = getConnection();
boolean invalidateConnectionOnRelease = false;
try {
IMAPFolder folder = getFolder(baseFolder, foldername);
return folder.exists();
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public int getNumberOfFilesInFolder(String foldername) throws FileSystemException {
IMAPFolder baseFolder = getConnection();
boolean invalidateConnectionOnRelease = false;
try {
IMAPFolder folder = getFolder(baseFolder, foldername);
if (!folder.isOpen()) {
folder.open(Folder.READ_WRITE);
}
Message[] messages = folder.getMessages();
return messages.length;
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public DirectoryStream list(String foldername, TypeFilter filter) throws FileSystemException {
if (filter.includeFolders()) {
throw new FileSystemException("Filtering on folders is not supported");
}
IMAPFolder baseFolder = getConnection();
if (baseFolder == null) {
return null;
}
boolean invalidateConnectionOnRelease = false;
try {
IMAPFolder folder = getFolder(baseFolder, foldername);
if (!folder.isOpen()) {
folder.open(Folder.READ_WRITE);
}
Message[] messages = folder.getMessages();
return FileSystemUtils.getDirectoryStream(Arrays.asList(messages));
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public void deleteFile(Message f) throws FileSystemException {
try {
f.setFlag(Flags.Flag.DELETED, true);
} catch (MessagingException e) {
throw new FileSystemException("Could not delete message [" + getCanonicalNameOrErrorMessage(f) + "]: " + e.getMessage());
}
}
@Override
public Message moveFile(Message f, String destinationFolder, boolean createFolder) throws FileSystemException {
IMAPFolder baseFolder = getConnection();
boolean invalidateConnectionOnRelease = false;
try {
AppendUID[] results;
try (IMAPFolder destination = getFolder(baseFolder, destinationFolder)) {
Message[] messages = new Message[1];
messages[0] = f;
destination.open(Folder.READ_WRITE);
IMAPFolder src = (IMAPFolder) f.getFolder();
results = src.moveUIDMessages(messages, destination);
}
if (results[0] == null) {
log.warn("could not find new name of message in folder [{}]", destinationFolder);
return null;
}
IMAPFolder destination = getFolder(baseFolder, destinationFolder);
destination.open(Folder.READ_WRITE);
return destination.getMessageByUID(results[0].uid);
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public Message copyFile(final Message f, String destinationFolder, boolean createFolder) throws FileSystemException {
IMAPFolder baseFolder = getConnection();
boolean invalidateConnectionOnRelease = false;
try {
AppendUID[] results;
try (IMAPFolder destination = getFolder(baseFolder, destinationFolder)) {
Message[] messages = new Message[1];
messages[0] = f;
destination.open(Folder.READ_WRITE);
IMAPFolder src = (IMAPFolder) f.getFolder();
results = src.copyUIDMessages(messages, destination);
}
if (results[0] == null) {
log.warn("could not find new name of message in folder [{}]", destinationFolder);
return null;
}
IMAPFolder destination = getFolder(baseFolder, destinationFolder);
destination.open(Folder.READ_WRITE);
return destination.getMessageByUID(results[0].uid);
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public void createFolder(String folderName) throws FileSystemException {
IMAPFolder baseFolder = getConnection();
boolean invalidateConnectionOnRelease = false;
try {
IMAPFolder folder = getFolder(baseFolder, folderName);
if (!folder.create(Folder.HOLDS_FOLDERS + Folder.HOLDS_MESSAGES)) {
throw new FileSystemException("Could not create folder [" + folderName + "]");
}
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public void removeFolder(String folderName, boolean removeNonEmptyFolder) throws FileSystemException {
IMAPFolder baseFolder = getConnection();
boolean invalidateConnectionOnRelease = false;
try {
IMAPFolder folder = getFolder(baseFolder, folderName);
if (folder == null) {
throw new FolderNotFoundException("Could not find folder object [" + folderName + "]");
}
if (!folder.delete(removeNonEmptyFolder)) {
throw new FileSystemException("Could not delete folder [" + folderName + "]");
}
} catch (MessagingException e) {
invalidateConnectionOnRelease = true;
throw new FileSystemException(e);
} finally {
releaseConnection(baseFolder, invalidateConnectionOnRelease);
}
}
@Override
public org.frankframework.stream.Message readFile(Message f, String charset) throws FileSystemException, IOException {
try {
Object content = f.getContent();
if (content instanceof MimeMultipart mimeMultipart) {
for (int i = 0; i < mimeMultipart.getCount(); i++) {
MimeBodyPart bodyPart = (MimeBodyPart) mimeMultipart.getBodyPart(i);
if (bodyPart.getContentType().startsWith("text/html")) {
return new PartMessage(bodyPart, charset);
}
}
for (int i = 0; i < mimeMultipart.getCount(); i++) {
BodyPart bodyPart = mimeMultipart.getBodyPart(i);
if (bodyPart.getContentType().startsWith("text")) {
return new PartMessage(bodyPart, charset);
}
}
}
return new PartMessage(f, FileSystemUtils.getContext(this, f, charset));
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public Iterator listAttachments(Message f) throws FileSystemException {
try {
String contentType = f.getContentType();
if (!contentType.contains("multipart")) {
return null;
}
Multipart multiPart = (Multipart) f.getContent();
return new Iterator<>() {
MimeBodyPart part = null;
int i = 0;
private void findPart() {
try {
while ((part == null) && i < multiPart.getCount()) {
part = (MimeBodyPart) multiPart.getBodyPart(i++);
if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
return;
}
part = null;
}
} catch (MessagingException e) {
log.warn("unable to find part", e);
}
}
@Override
public boolean hasNext() {
findPart();
return part != null;
}
@Override
public MimeBodyPart next() {
findPart();
MimeBodyPart result = part;
part = null;
return result;
}
};
} catch (MessagingException | IOException e) {
throw new FileSystemException(e);
}
}
@Override
public String getAttachmentName(MimeBodyPart a) throws FileSystemException {
try {
String name = a.getContentID();
return name == null ? "" : name;
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public org.frankframework.stream.Message readAttachment(MimeBodyPart a) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getAttachmentSize(MimeBodyPart a) throws FileSystemException {
try {
return a.getSize();
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public String getAttachmentContentType(MimeBodyPart a) throws FileSystemException {
try {
return a.getContentType();
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public String getAttachmentFileName(MimeBodyPart a) throws FileSystemException {
try {
return a.getFileName();
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public Message getFileFromAttachment(MimeBodyPart a) throws FileSystemException {
try {
Object content = a.getContent();
if (content instanceof Message message) {
return message;
}
return null;
} catch (MessagingException | IOException e) {
throw new FileSystemException(e);
}
}
@Override
public Map getAdditionalAttachmentProperties(MimeBodyPart a) throws FileSystemException {
try {
Map result = new LinkedHashMap<>();
for (Enumeration headers = a.getAllHeaders(); headers.hasMoreElements();) {
Header header = headers.nextElement();
result.put(header.getName(), header.getValue());
}
return result;
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public long getFileSize(Message f) throws FileSystemException {
try {
return f.getSize();
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public String getCanonicalName(Message f) {
return getName(f);
}
@Override
public Date getModificationTime(Message f) {
return null;
}
private List getRecipientsOfType(Message f, RecipientType type) throws MessagingException {
InternetAddress[] recipients = (InternetAddress[]) f.getRecipients(type);
if (recipients == null) {
return Collections.emptyList();
}
return Arrays.stream(recipients).map(InternetAddress::toUnicodeString).toList();
}
private List getReplyTo(Message f) throws MessagingException {
InternetAddress[] recipients = (InternetAddress[]) f.getReplyTo();
if (recipients == null) {
return Collections.emptyList();
}
return Arrays.stream(recipients).map(InternetAddress::toUnicodeString).toList();
}
@Override
@Nullable
public Map getAdditionalFileProperties(Message f) throws FileSystemException {
try {
Map result = new LinkedHashMap<>();
result.put(TO_RECIPIENTS_KEY, getRecipientsOfType(f, RecipientType.TO));
result.put(CC_RECIPIENTS_KEY, getRecipientsOfType(f, RecipientType.CC));
result.put(BCC_RECIPIENTS_KEY, getRecipientsOfType(f, RecipientType.BCC));
result.put(FROM_ADDRESS_KEY, InternetAddress.toUnicodeString(f.getFrom()));
// result.put(IMailFileSystem.SENDER_ADDRESS_KEY, f.getS);
result.put(REPLY_TO_RECIPIENTS_KEY, getReplyTo(f));
result.put(DATETIME_SENT_KEY, f.getSentDate());
result.put(DATETIME_RECEIVED_KEY, f.getReceivedDate());
for (Enumeration headerEnum = f.getAllHeaders(); headerEnum.hasMoreElements();) {
Header header = headerEnum.nextElement();
result.put(header.getName(), header.getValue());
}
result.put(BEST_REPLY_ADDRESS_KEY, MailFileSystemUtils.findBestReplyAddress(result, getReplyAddressFields()));
return result;
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public void forwardMail(Message emailMessage, String destination) throws FileSystemException {
try {
Message forward = new MimeMessage(emailSession);
// Fill in header
forward.setRecipients(Message.RecipientType.TO, InternetAddress.parse(destination));
forward.setSubject("Fwd: " + emailMessage.getSubject());
//forward.setFrom(new InternetAddress(emailMessage.get));
// Create the message part
MimeBodyPart messageBodyPart = new MimeBodyPart();
// Create a multipart message
Multipart multipart = new MimeMultipart();
// set content
messageBodyPart.setContent(emailMessage, "message/rfc822");
// Add part to multi part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
forward.setContent(multipart);
forward.saveChanges();
// Send the message by authenticating the SMTP server
// Create a Transport instance and call the sendMessage
try (Transport t = emailSession.getTransport("smtp")) {
CredentialFactory cf = new CredentialFactory(getAuthAlias(), getUsername(), getPassword());
// connect to the smpt server using transport instance
t.connect(cf.getUsername(), cf.getPassword());
t.sendMessage(forward, forward.getAllRecipients());
}
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public String getPhysicalDestinationName() {
URLName urlName = null;
if (isOpen()) {
try {
IMAPFolder baseFolder = getConnection();
urlName = baseFolder.getStore().getURLName();
releaseConnection(baseFolder, false);
} catch (FileSystemException e) {
log.warn("cannot get urlName", e);
}
}
String name = urlName == null ? "" : urlName.toString();
return StringUtil.concatStrings(name," ", super.getPhysicalDestinationName());
}
@Override
public String getSubject(Message emailMessage) throws FileSystemException {
try {
return emailMessage.getSubject();
} catch (MessagingException e) {
throw new FileSystemException(e);
}
}
@Override
public org.frankframework.stream.Message getMimeContent(Message emailMessage) {
return new MimeContentMessage((IMAPMessage) emailMessage);
}
private static class MimeContentMessage extends org.frankframework.stream.Message {
public MimeContentMessage(IMAPMessage imapMessage) {
super(imapMessage::getMimeStream, null, imapMessage.getClass());
}
}
@Override
public void extractEmail(Message emailMessage, SaxElementBuilder emailXml)
throws FileSystemException, SAXException {
MailFileSystemUtils.addEmailInfo(this, emailMessage, emailXml);
}
@Override
public void extractAttachment(MimeBodyPart attachment, SaxElementBuilder attachmentsXml)
throws FileSystemException, SAXException {
MailFileSystemUtils.addAttachmentInfo(this, attachment, attachmentsXml);
}
/**
* The hostname of the IMAP server
* @ff.mandatory
*/
public void setHost(String host) {
this.host = host;
}
/**
* The port of the IMAP server
* @ff.default 993
*/
public void setPort(int port) {
this.port = port;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy