Browse Source

云帆bug修复

24282 2 years ago
parent
commit
767d58744a
25 changed files with 4712 additions and 4809 deletions
  1. 771 788
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/CommonEmail.java
  2. 94 99
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EMLComposer.java
  3. 80 85
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EMLParser.java
  4. 97 98
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EMLProperties.java
  5. 145 145
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/Email.java
  6. 193 193
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailAddress.java
  7. 257 272
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailAttachment.java
  8. 242 246
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailAttachmentBuilder.java
  9. 343 358
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailFilter.java
  10. 58 58
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailMessage.java
  11. 176 182
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailUtil.java
  12. 40 40
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ImapSslServer.java
  13. 25 25
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/MailException.java
  14. 335 337
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/MailServer.java
  15. 79 83
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/MailSession.java
  16. 59 59
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/Pop3Server.java
  17. 38 38
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/Pop3SslServer.java
  18. 645 650
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/RFC2822AddressParser.java
  19. 374 382
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ReceiveMailSession.java
  20. 96 102
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ReceivedEmail.java
  21. 96 95
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ReceiverBuilder.java
  22. 312 316
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SendMailSession.java
  23. 21 21
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SimpleAuthenticator.java
  24. 69 68
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SmtpServer.java
  25. 67 69
      bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SmtpSslServer.java

+ 771 - 788
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/CommonEmail.java

@@ -27,800 +27,783 @@ package com.fjhx.utils.mail;
 
 import jakarta.activation.DataSource;
 import jakarta.mail.Address;
-import jodd.util.ArraysUtil;
+import jakarta.mail.Header;
 import jodd.net.MimeTypes;
+import jodd.util.ArraysUtil;
 
-import jakarta.mail.Header;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * Common stuff for both {@link Email} and {@link ReceivedEmail}.
  */
 public abstract class CommonEmail<T extends CommonEmail<T>> {
 
-	@SuppressWarnings("unchecked")
-	protected T _this() {
-		return (T) this;
-	}
-
-	public static final String X_PRIORITY = "X-Priority";
-
-	public static final int PRIORITY_HIGHEST = 1;
-	public static final int PRIORITY_HIGH = 2;
-	public static final int PRIORITY_NORMAL = 3;
-	public static final int PRIORITY_LOW = 4;
-	public static final int PRIORITY_LOWEST = 5;
-
-	/**
-	 * Clones the email with all its necessary data.
-	 *
-	 * @return new object of type T
-	 */
-	@Override
-	public abstract T clone();
-
-	// ---------------------------------------------------------------- from
-
-	/**
-	 * FROM address.
-	 */
-	private EmailAddress from;
-
-	/**
-	 * Sets the FROM address.
-	 *
-	 * @param from {@link EmailAddress}.
-	 * @return this
-	 */
-	public T from(final EmailAddress from) {
-		this.from = from;
-		return _this();
-	}
-
-	/**
-	 * Sets the FROM address from {@link Address}.
-	 *
-	 * @param from {@link Address}
-	 * @return this
-	 * @see #from(EmailAddress)
-	 */
-	public T from(final Address from) {
-		return from(EmailAddress.of(from));
-	}
-
-	/**
-	 * Sets the FROM address.
-	 *
-	 * @param from Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
-	 * @return this
-	 * @see #from(EmailAddress)
-	 */
-	public T from(final String from) {
-		return from(EmailAddress.of(from));
-	}
-
-	/**
-	 * Sets the FROM address by providing personal name and address.
-	 *
-	 * @param personalName personal name.
-	 * @param from         email address.
-	 * @return this
-	 * @see #from(EmailAddress)
-	 */
-	public T from(final String personalName, final String from) {
-		return from(new EmailAddress(personalName, from));
-	}
-
-	/**
-	 * Returns FROM {@link EmailAddress}.
-	 */
-	public EmailAddress from() {
-		return from;
-	}
-
-	// ---------------------------------------------------------------- to
-
-	/**
-	 * TO address.
-	 */
-	private EmailAddress[] to = EmailAddress.EMPTY_ARRAY;
-
-	/**
-	 * Appends TO address.
-	 *
-	 * @param to {@link EmailAddress} to add.
-	 * @return this
-	 */
-	public T to(final EmailAddress to) {
-		this.to = ArraysUtil.append(this.to, to);
-		return _this();
-	}
-
-	/**
-	 * Appends TO address.
-	 *
-	 * @param to Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
-	 * @return this
-	 * @see #to(EmailAddress)
-	 */
-	public T to(final String to) {
-		return to(EmailAddress.of(to));
-	}
-
-	/**
-	 * Appends TO address by personal name and email address.
-	 *
-	 * @param personalName personal name.
-	 * @param to           email address.
-	 * @return this
-	 * @see #to(EmailAddress)
-	 */
-	public T to(final String personalName, final String to) {
-		return to(new EmailAddress(personalName, to));
-	}
-
-	/**
-	 * Appends TO address from {@code Address}.
-	 *
-	 * @param to {@link Address} to add.
-	 * @return this
-	 * @see #to(EmailAddress)
-	 */
-	public T to(final Address to) {
-		return to(EmailAddress.of(to));
-	}
-
-	/**
-	 * Appends one or more TO address.
-	 *
-	 * @param tos Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
-	 * @return this
-	 * @see #to(EmailAddress...)
-	 */
-	public T to(final String... tos) {
-		return to(EmailAddress.of(tos));
-	}
-
-	/**
-	 * Appends one or more TO addresses.
-	 *
-	 * @param tos array of {@link Address}s to set.
-	 * @return this
-	 * @see #to(EmailAddress...)
-	 */
-	public T to(final Address... tos) {
-		return to(EmailAddress.of(tos));
-	}
-
-	/**
-	 * Appends TO addresses.
-	 *
-	 * @param tos vararg of {@link EmailAddress}es to set.
-	 * @return this
-	 */
-	public T to(final EmailAddress... tos) {
-		this.to = valueOrEmptyArray(tos);
-		return _this();
-	}
-
-	/**
-	 * Returns TO addresses.
-	 */
-	public EmailAddress[] to() {
-		return to;
-	}
-
-	/**
-	 * Resets TO addresses.
-	 */
-	public T resetTo() {
-		this.to = EmailAddress.EMPTY_ARRAY;
-		return _this();
-	}
-
-	// ---------------------------------------------------------------- reply-to
-
-	private EmailAddress[] replyTo = EmailAddress.EMPTY_ARRAY;
-
-	/**
-	 * Appends REPLY-TO address.
-	 *
-	 * @param replyTo {@link EmailAddress} to add.
-	 * @return this
-	 */
-	public T replyTo(final EmailAddress replyTo) {
-		this.replyTo = ArraysUtil.append(this.replyTo, replyTo);
-		return _this();
-	}
-
-	/**
-	 * Appends REPLY-TO address.
-	 *
-	 * @param replyTo Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
-	 * @return this
-	 * @see #replyTo(EmailAddress)
-	 */
-	public T replyTo(final String replyTo) {
-		return replyTo(EmailAddress.of(replyTo));
-	}
-
-	/**
-	 * Appends REPLY-TO address.
-	 *
-	 * @param personalName personal name.
-	 * @param replyTo      email address.
-	 * @return this
-	 * @see #replyTo(EmailAddress)
-	 */
-	public T replyTo(final String personalName, final String replyTo) {
-		return replyTo(new EmailAddress(personalName, replyTo));
-	}
-
-	/**
-	 * Appends REPLY-TO address.
-	 *
-	 * @param replyTo {@link Address} to add.
-	 * @return this
-	 * @see #replyTo(EmailAddress)
-	 */
-	public T replyTo(final Address replyTo) {
-		return replyTo(EmailAddress.of(replyTo));
-	}
-
-	/**
-	 * Appends one or more REPLY-TO address.
-	 *
-	 * @param replyTos array of {@link EmailAddress}es to set.
-	 * @return this
-	 * @see #replyTo(EmailAddress...)
-	 */
-	public T replyTo(final String... replyTos) {
-		return replyTo(EmailAddress.of(replyTos));
-	}
-
-	/**
-	 * Appeds one or more REPLY-TO address.
-	 *
-	 * @param replyTos array of {@link Address}es to set.
-	 * @return this
-	 * @see #replyTo(EmailAddress...)
-	 */
-	public T replyTo(final Address... replyTos) {
-		return replyTo(EmailAddress.of(replyTos));
-	}
-
-	/**
-	 * Appends REPLY-TO addresses.
-	 *
-	 * @param replyTo vararg of {@link EmailAddress}es to set.
-	 * @return this
-	 */
-	public T replyTo(final EmailAddress... replyTo) {
-		this.replyTo = ArraysUtil.join(this.replyTo, valueOrEmptyArray(replyTo));
-		return _this();
-	}
-
-	/**
-	 * Returns REPLY-TO addresses.
-	 */
-	public EmailAddress[] replyTo() {
-		return replyTo;
-	}
-
-	/**
-	 * Resets all REPLY-To addresses.
-	 */
-	public T resetReplyTo() {
-		this.replyTo = EmailAddress.EMPTY_ARRAY;
-		return _this();
-	}
-
-	// ---------------------------------------------------------------- cc
-
-	/**
-	 * CC address.
-	 */
-	private EmailAddress[] cc = EmailAddress.EMPTY_ARRAY;
-
-	/**
-	 * Appends CC address.
-	 *
-	 * @param to {@link EmailAddress} to add.
-	 * @return this
-	 */
-	public T cc(final EmailAddress to) {
-		this.cc = ArraysUtil.append(this.cc, to);
-		return _this();
-	}
-
-	/**
-	 * Appends CC address.
-	 *
-	 * @param cc Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
-	 * @return this
-	 * @see #cc(EmailAddress)
-	 */
-	public T cc(final String cc) {
-		return cc(EmailAddress.of(cc));
-	}
-
-	/**
-	 * Appends CC address.
-	 *
-	 * @param personalName personal name.
-	 * @param cc           email address.
-	 * @return this
-	 * @see #cc(EmailAddress)
-	 */
-	public T cc(final String personalName, final String cc) {
-		return cc(new EmailAddress(personalName, cc));
-	}
-
-	/**
-	 * Appends CC address.
-	 *
-	 * @param cc {@link Address} to add.
-	 * @return this
-	 * @see #cc(EmailAddress)
-	 */
-	public T cc(final Address cc) {
-		return cc(EmailAddress.of(cc));
-	}
-
-	/**
-	 * Sets one or more CC address.
-	 *
-	 * @param ccs array of {@link String}s to set.
-	 * @return this
-	 * @see #cc(EmailAddress...)
-	 */
-	public T cc(final String... ccs) {
-		return cc(EmailAddress.of(ccs));
-	}
-
-	/**
-	 * Sets one or more CC address.
-	 *
-	 * @param ccs array of {@link Address}s to set.
-	 * @return this
-	 * @see #cc(EmailAddress...)
-	 */
-	public T cc(final Address... ccs) {
-		return cc(EmailAddress.of(ccs));
-	}
-
-	/**
-	 * Appends CC addresses.
-	 *
-	 * @param ccs vararg of {@link EmailAddress}es to set.
-	 * @return this
-	 */
-	public T cc(final EmailAddress... ccs) {
-		this.cc = ArraysUtil.join(this.cc, valueOrEmptyArray(ccs));
-		return _this();
-	}
-
-	/**
-	 * Returns CC addresses.
-	 */
-	public EmailAddress[] cc() {
-		return cc;
-	}
-
-	/**
-	 * Resets all CC addresses.
-	 */
-	public T resetCc() {
-		this.cc = EmailAddress.EMPTY_ARRAY;
-		return _this();
-	}
-
-	// ---------------------------------------------------------------- subject
-
-	/**
-	 * Message subject.
-	 */
-	private String subject;
-
-	/**
-	 * Message subject encoding.
-	 */
-	private String subjectEncoding;
-
-	/**
-	 * Sets message subject.
-	 *
-	 * @param subject The message subject to set.
-	 * @return this
-	 */
-	public T subject(final String subject) {
-		this.subject = subject;
-		return _this();
-	}
-
-	/**
-	 * Sets message subject with specified encoding to override default platform encoding.
-	 * If the subject contains non US-ASCII characters, it will be encoded using the specified charset.
-	 * If the subject contains only US-ASCII characters, no encoding is done and it is used as-is.
-	 * The application must ensure that the subject does not contain any line breaks.
-	 *
-	 * @param subject  The message subject
-	 * @param encoding The encoding for the message subject.
-	 * @return this
-	 */
-	public T subject(final String subject, final String encoding) {
-		subject(subject);
-		this.subjectEncoding = encoding;
-		return _this();
-	}
-
-	/**
-	 * Returns message subject.
-	 *
-	 * @return message subject.
-	 */
-	public String subject() {
-		return this.subject;
-	}
-
-	/**
-	 * Returns the message subject encoding.
-	 *
-	 * @return the message subject encoding.
-	 */
-	public String subjectEncoding() {
-		return this.subjectEncoding;
-	}
-
-	// ---------------------------------------------------------------- message
-
-	/**
-	 * All messages.
-	 */
-	private final List<EmailMessage> messages = new ArrayList<>();
-
-	/**
-	 * Returns all messages.
-	 */
-	public List<EmailMessage> messages() {
-		return messages;
-	}
-
-	/**
-	 * Adds multiple messages.
-	 *
-	 * @param msgsToAdd {@link List} of {@link EmailMessage}s to add.
-	 * @return this
-	 */
-	public T message(final List<EmailMessage> msgsToAdd) {
-		messages.addAll(msgsToAdd);
-		return _this();
-	}
-
-	/**
-	 * Adds an {@link EmailMessage}.
-	 *
-	 * @param msgToAdd {@link EmailMessage} to add.
-	 * @return this
-	 */
-	public T message(final EmailMessage msgToAdd) {
-		messages.add(msgToAdd);
-		return _this();
-	}
-
-	/**
-	 * Adds a {@link EmailMessage}.
-	 *
-	 * @param text     The text to add as a {@link String}.
-	 * @param mimeType The MIME type as a {@link String}.
-	 * @param encoding The encoding as a {@link String}.
-	 * @return this
-	 * @see #message(EmailMessage)
-	 */
-	public T message(final String text, final String mimeType, final String encoding) {
-		return message(new EmailMessage(text, mimeType, encoding));
-	}
-
-	/**
-	 * Adds a {@link EmailMessage}.
-	 *
-	 * @param text     The text to add as a {@link String}.
-	 * @param mimeType The MIME type as a {@link String}.
-	 * @return this
-	 * @see #message(EmailMessage)
-	 */
-	public T message(final String text, final String mimeType) {
-		return message(new EmailMessage(text, mimeType));
-	}
-
-	/**
-	 * Adds plain message text.
-	 *
-	 * @param text The text to add as a {@link String}.
-	 * @return this
-	 * @see #message(String, String)
-	 */
-	public T textMessage(final String text) {
-		return message(text, MimeTypes.MIME_TEXT_PLAIN);
-	}
-
-	/**
-	 * Adds plain message text.
-	 *
-	 * @param text     The text to add as a {@link String}.
-	 * @param encoding The encoding as a {@link String}.
-	 * @return this
-	 * @see #message(EmailMessage)
-	 */
-	public T textMessage(final String text, final String encoding) {
-		return message(new EmailMessage(text, MimeTypes.MIME_TEXT_PLAIN, encoding));
-	}
-
-	/**
-	 * Adds HTML message.
-	 *
-	 * @param html The HTML to add as a {@link String}.
-	 * @return this
-	 * @see #message(EmailMessage)
-	 */
-	public T htmlMessage(final String html) {
-		return message(new EmailMessage(html, MimeTypes.MIME_TEXT_HTML));
-	}
-
-	/**
-	 * Adds HTML message.
-	 *
-	 * @param html     The HTML to add as a {@link String}.
-	 * @param encoding The encoding as a {@link String}.
-	 * @return this
-	 * @see #message(EmailMessage)
-	 */
-	public T htmlMessage(final String html, final String encoding) {
-		return message(new EmailMessage(html, MimeTypes.MIME_TEXT_HTML, encoding));
-	}
-
-	// ---------------------------------------------------------------- headers
-
-	/**
-	 * All headers.
-	 */
-	private final Map<String, String> headers = new HashMap<>();
-
-	/**
-	 * Returns all headers as a {@link HashMap}.
-	 *
-	 * @return all headers in a {@link HashMap}
-	 */
-	protected Map<String, String> headers() {
-		return headers;
-	}
-
-	/**
-	 * Sets header value.
-	 *
-	 * @param name  The name of the header.
-	 * @param value The value of the header.
-	 * @return this
-	 */
-	public T header(final String name, final String value) {
-		headers.put(name, value);
-		return _this();
-	}
-
-	/**
-	 * Sets headers.
-	 *
-	 * @param headersToSet Headers to set.
-	 * @return this
-	 */
-	public T headers(final Map<String, String> headersToSet) {
-		headers.putAll(headersToSet);
-		return _this();
-	}
-
-
-	/**
-	 * Sets headers.
-	 *
-	 * @param headersToSet Headers to set.
-	 * @return this
-	 * @see #header(String, String)
-	 */
-	public T headers(final Enumeration<Header> headersToSet) {
-		while (headersToSet.hasMoreElements()) {
-			final Header header = headersToSet.nextElement();
-			header(header.getName(), header.getValue());
-		}
-		return _this();
-	}
-
-	/**
-	 * Returns the value of a header.
-	 *
-	 * @param name The name of the header.
-	 * @return The value of the header.
-	 */
-	public String header(final String name) {
-		return headers.get(name);
-	}
-
-	/**
-	 * Sets email priority.
-	 *
-	 * @param priority - Values of 1 through 5 are acceptable, with 1 being the highest priority, 3 = normal
-	 *                 and 5 = lowest priority.
-	 */
-	public T priority(final int priority) {
-		header(X_PRIORITY, String.valueOf(priority));
-		return _this();
-	}
-
-	/**
-	 * Returns emails priority (1 - 5) or <code>-1</code> if priority not available.
-	 *
-	 * @see #priority(int)
-	 */
-	public int priority() {
-		try {
-			return Integer.parseInt(headers.get(X_PRIORITY));
-		} catch (final NumberFormatException ignore) {
-			return -1;
-		}
-	}
-
-	// ---------------------------------------------------------------- attachments
-
-	private final List<EmailAttachment<? extends DataSource>> attachments = new ArrayList<>();
-
-	/**
-	 * Returns the list of all {@link EmailAttachment}s.
-	 *
-	 * @return List of {@link EmailAttachment}s. Returns empty list if no attachment is available.
-	 */
-	public List<EmailAttachment<? extends DataSource>> attachments() {
-		return attachments;
-	}
-
-	/**
-	 * Adds {@link EmailAttachment}s.
-	 *
-	 * @param attachments {@link List} of {@link EmailAttachment}s to add.
-	 * @return this
-	 */
-	protected T storeAttachments(final List<EmailAttachment<? extends DataSource>> attachments) {
-		this.attachments.addAll(attachments);
-		return _this();
-	}
-
-	/**
-	 * Adds {@link EmailAttachment}.
-	 *
-	 * @param attachment {@link EmailAttachment} to add.
-	 * @return this
-	 */
-	protected T storeAttachment(final EmailAttachment<? extends DataSource> attachment) {
-		this.attachments.add(attachment);
-		return _this();
-	}
-
-	/**
-	 * Adds {@link EmailAttachment}s.
-	 *
-	 * @param attachments {@link List} of {@link EmailAttachment}s to add.
-	 * @return this
-	 */
-	public T attachments(final List<EmailAttachment<? extends DataSource>> attachments) {
-		for (final EmailAttachment<?> attachment : attachments) {
-			attachment(attachment);
-		}
-		return _this();
-	}
-
-	/**
-	 * Adds {@link EmailAttachment}. Content ID will be set to {@code null}.
-	 *
-	 * @param attachment {@link EmailAttachment} to add.
-	 * @return this
-	 */
-	public T attachment(final EmailAttachment<? extends DataSource> attachment) {
-		attachment.setContentId(null);
-		return storeAttachment(attachment);
-	}
-
-	/**
-	 * @see #attachment(EmailAttachment)
-	 */
-	public T attachment(final EmailAttachmentBuilder builder) {
-		return attachment(builder.buildByteArrayDataSource());
-	}
-
-	/**
-	 * Attaches the embedded attachment: Content ID will be set if missing from attachment's file name.
-	 *
-	 * @param builder {@link EmailAttachmentBuilder}
-	 * @return this
-	 * @see #embeddedAttachment(EmailAttachment)
-	 */
-	public T embeddedAttachment(final EmailAttachmentBuilder builder) {
-		builder.setContentIdFromNameIfMissing();
-
-		// https://github.com/oblac/jodd/issues/546
-		// https://github.com/oblac/jodd/issues/404#issuecomment-297011351
-		// content disposition will be set to "inline"
-		builder.inline(true);
-
-		return embeddedAttachment(builder.buildByteArrayDataSource());
-	}
-
-	/**
-	 * Embed {@link EmailAttachment} to last message. No header is changed.
-	 *
-	 * @param attachment {@link EmailAttachment}
-	 * @return this
-	 * @see #storeAttachment(EmailAttachment)
-	 */
-	public T embeddedAttachment(final EmailAttachment<? extends DataSource> attachment) {
-		storeAttachment(attachment);
-
-		final List<EmailMessage> messages = messages();
-		final int size = messages.size();
-		if (size > 1) {
-			// Add to last message
-			final int lastMessagePos = size - 1;
-			final EmailMessage lastMessage = messages.get(lastMessagePos);
-			attachment.setEmbeddedMessage(lastMessage);
-		}
-
-		return _this();
-	}
-
-	// ---------------------------------------------------------------- date
-
-	/**
-	 * Email's sent date.
-	 */
-	private Date sentDate;
-
-	/**
-	 * Sets email's sent date.
-	 *
-	 * @param date - Email's sent date. If {@code null}, then date will be set during the process of sending.
-	 * @return this
-	 */
-	public T sentDate(final Date date) {
-		sentDate = date;
-		return _this();
-	}
-
-
-	/**
-	 * Returns email's sent date. If return value is {@code null}, then date
-	 * will be set during the process of sending.
-	 *
-	 * @return email's sent date or {@code null} if it will be set later.
-	 */
-	public Date sentDate() {
-		return sentDate;
-	}
-
-	// ---------------------------------------------------------------- toString
-
-	@Override
-	public String toString() {
-		return "Email{'" + from() + "\', subject='" + subject() + "\'}";
-	}
-
-	// ---------------------------------------------------------------- helper
-
-	protected EmailAddress[] valueOrEmptyArray(EmailAddress[] arr) {
-		if (arr == null) {
-			arr = EmailAddress.EMPTY_ARRAY;
-		}
-		return arr;
-	}
+    public static final String X_PRIORITY = "X-Priority";
+    public static final int PRIORITY_HIGHEST = 1;
+    public static final int PRIORITY_HIGH = 2;
+    public static final int PRIORITY_NORMAL = 3;
+    public static final int PRIORITY_LOW = 4;
+    public static final int PRIORITY_LOWEST = 5;
+    /**
+     * All messages.
+     */
+    private final List<EmailMessage> messages = new ArrayList<>();
+    /**
+     * All headers.
+     */
+    private final Map<String, String> headers = new HashMap<>();
+
+    // ---------------------------------------------------------------- from
+    private final List<EmailAttachment<? extends DataSource>> attachments = new ArrayList<>();
+    /**
+     * FROM address.
+     */
+    private EmailAddress from;
+    /**
+     * TO address.
+     */
+    private EmailAddress[] to = EmailAddress.EMPTY_ARRAY;
+    private EmailAddress[] replyTo = EmailAddress.EMPTY_ARRAY;
+    /**
+     * CC address.
+     */
+    private EmailAddress[] cc = EmailAddress.EMPTY_ARRAY;
+    /**
+     * Message subject.
+     */
+    private String subject;
+
+    // ---------------------------------------------------------------- to
+    /**
+     * Message subject encoding.
+     */
+    private String subjectEncoding;
+    /**
+     * Email's sent date.
+     */
+    private Date sentDate;
+
+    @SuppressWarnings("unchecked")
+    protected T _this() {
+        return (T) this;
+    }
+
+    /**
+     * Clones the email with all its necessary data.
+     *
+     * @return new object of type T
+     */
+    @Override
+    public abstract T clone();
+
+    /**
+     * Sets the FROM address.
+     *
+     * @param from {@link EmailAddress}.
+     * @return this
+     */
+    public T from(final EmailAddress from) {
+        this.from = from;
+        return _this();
+    }
+
+    /**
+     * Sets the FROM address from {@link Address}.
+     *
+     * @param from {@link Address}
+     * @return this
+     * @see #from(EmailAddress)
+     */
+    public T from(final Address from) {
+        return from(EmailAddress.of(from));
+    }
+
+    /**
+     * Sets the FROM address.
+     *
+     * @param from Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
+     * @return this
+     * @see #from(EmailAddress)
+     */
+    public T from(final String from) {
+        return from(EmailAddress.of(from));
+    }
+
+    /**
+     * Sets the FROM address by providing personal name and address.
+     *
+     * @param personalName personal name.
+     * @param from         email address.
+     * @return this
+     * @see #from(EmailAddress)
+     */
+    public T from(final String personalName, final String from) {
+        return from(new EmailAddress(personalName, from));
+    }
+
+    /**
+     * Returns FROM {@link EmailAddress}.
+     */
+    public EmailAddress from() {
+        return from;
+    }
+
+    /**
+     * Appends TO address.
+     *
+     * @param to {@link EmailAddress} to add.
+     * @return this
+     */
+    public T to(final EmailAddress to) {
+        this.to = ArraysUtil.append(this.to, to);
+        return _this();
+    }
+
+    // ---------------------------------------------------------------- reply-to
+
+    /**
+     * Appends TO address.
+     *
+     * @param to Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
+     * @return this
+     * @see #to(EmailAddress)
+     */
+    public T to(final String to) {
+        return to(EmailAddress.of(to));
+    }
+
+    /**
+     * Appends TO address by personal name and email address.
+     *
+     * @param personalName personal name.
+     * @param to           email address.
+     * @return this
+     * @see #to(EmailAddress)
+     */
+    public T to(final String personalName, final String to) {
+        return to(new EmailAddress(personalName, to));
+    }
+
+    /**
+     * Appends TO address from {@code Address}.
+     *
+     * @param to {@link Address} to add.
+     * @return this
+     * @see #to(EmailAddress)
+     */
+    public T to(final Address to) {
+        return to(EmailAddress.of(to));
+    }
+
+    /**
+     * Appends one or more TO address.
+     *
+     * @param tos Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
+     * @return this
+     * @see #to(EmailAddress...)
+     */
+    public T to(final String... tos) {
+        return to(EmailAddress.of(tos));
+    }
+
+    /**
+     * Appends one or more TO addresses.
+     *
+     * @param tos array of {@link Address}s to set.
+     * @return this
+     * @see #to(EmailAddress...)
+     */
+    public T to(final Address... tos) {
+        return to(EmailAddress.of(tos));
+    }
+
+    /**
+     * Appends TO addresses.
+     *
+     * @param tos vararg of {@link EmailAddress}es to set.
+     * @return this
+     */
+    public T to(final EmailAddress... tos) {
+        this.to = valueOrEmptyArray(tos);
+        return _this();
+    }
+
+    /**
+     * Returns TO addresses.
+     */
+    public EmailAddress[] to() {
+        return to;
+    }
+
+    /**
+     * Resets TO addresses.
+     */
+    public T resetTo() {
+        this.to = EmailAddress.EMPTY_ARRAY;
+        return _this();
+    }
+
+    /**
+     * Appends REPLY-TO address.
+     *
+     * @param replyTo {@link EmailAddress} to add.
+     * @return this
+     */
+    public T replyTo(final EmailAddress replyTo) {
+        this.replyTo = ArraysUtil.append(this.replyTo, replyTo);
+        return _this();
+    }
+
+    /**
+     * Appends REPLY-TO address.
+     *
+     * @param replyTo Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
+     * @return this
+     * @see #replyTo(EmailAddress)
+     */
+    public T replyTo(final String replyTo) {
+        return replyTo(EmailAddress.of(replyTo));
+    }
+
+    // ---------------------------------------------------------------- cc
+
+    /**
+     * Appends REPLY-TO address.
+     *
+     * @param personalName personal name.
+     * @param replyTo      email address.
+     * @return this
+     * @see #replyTo(EmailAddress)
+     */
+    public T replyTo(final String personalName, final String replyTo) {
+        return replyTo(new EmailAddress(personalName, replyTo));
+    }
+
+    /**
+     * Appends REPLY-TO address.
+     *
+     * @param replyTo {@link Address} to add.
+     * @return this
+     * @see #replyTo(EmailAddress)
+     */
+    public T replyTo(final Address replyTo) {
+        return replyTo(EmailAddress.of(replyTo));
+    }
+
+    /**
+     * Appends one or more REPLY-TO address.
+     *
+     * @param replyTos array of {@link EmailAddress}es to set.
+     * @return this
+     * @see #replyTo(EmailAddress...)
+     */
+    public T replyTo(final String... replyTos) {
+        return replyTo(EmailAddress.of(replyTos));
+    }
+
+    /**
+     * Appeds one or more REPLY-TO address.
+     *
+     * @param replyTos array of {@link Address}es to set.
+     * @return this
+     * @see #replyTo(EmailAddress...)
+     */
+    public T replyTo(final Address... replyTos) {
+        return replyTo(EmailAddress.of(replyTos));
+    }
+
+    /**
+     * Appends REPLY-TO addresses.
+     *
+     * @param replyTo vararg of {@link EmailAddress}es to set.
+     * @return this
+     */
+    public T replyTo(final EmailAddress... replyTo) {
+        this.replyTo = ArraysUtil.join(this.replyTo, valueOrEmptyArray(replyTo));
+        return _this();
+    }
+
+    /**
+     * Returns REPLY-TO addresses.
+     */
+    public EmailAddress[] replyTo() {
+        return replyTo;
+    }
+
+    /**
+     * Resets all REPLY-To addresses.
+     */
+    public T resetReplyTo() {
+        this.replyTo = EmailAddress.EMPTY_ARRAY;
+        return _this();
+    }
+
+    /**
+     * Appends CC address.
+     *
+     * @param to {@link EmailAddress} to add.
+     * @return this
+     */
+    public T cc(final EmailAddress to) {
+        this.cc = ArraysUtil.append(this.cc, to);
+        return _this();
+    }
+
+    /**
+     * Appends CC address.
+     *
+     * @param cc Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
+     * @return this
+     * @see #cc(EmailAddress)
+     */
+    public T cc(final String cc) {
+        return cc(EmailAddress.of(cc));
+    }
+
+    /**
+     * Appends CC address.
+     *
+     * @param personalName personal name.
+     * @param cc           email address.
+     * @return this
+     * @see #cc(EmailAddress)
+     */
+    public T cc(final String personalName, final String cc) {
+        return cc(new EmailAddress(personalName, cc));
+    }
+
+    // ---------------------------------------------------------------- subject
+
+    /**
+     * Appends CC address.
+     *
+     * @param cc {@link Address} to add.
+     * @return this
+     * @see #cc(EmailAddress)
+     */
+    public T cc(final Address cc) {
+        return cc(EmailAddress.of(cc));
+    }
+
+    /**
+     * Sets one or more CC address.
+     *
+     * @param ccs array of {@link String}s to set.
+     * @return this
+     * @see #cc(EmailAddress...)
+     */
+    public T cc(final String... ccs) {
+        return cc(EmailAddress.of(ccs));
+    }
+
+    /**
+     * Sets one or more CC address.
+     *
+     * @param ccs array of {@link Address}s to set.
+     * @return this
+     * @see #cc(EmailAddress...)
+     */
+    public T cc(final Address... ccs) {
+        return cc(EmailAddress.of(ccs));
+    }
+
+    /**
+     * Appends CC addresses.
+     *
+     * @param ccs vararg of {@link EmailAddress}es to set.
+     * @return this
+     */
+    public T cc(final EmailAddress... ccs) {
+        this.cc = ArraysUtil.join(this.cc, valueOrEmptyArray(ccs));
+        return _this();
+    }
+
+    /**
+     * Returns CC addresses.
+     */
+    public EmailAddress[] cc() {
+        return cc;
+    }
+
+    /**
+     * Resets all CC addresses.
+     */
+    public T resetCc() {
+        this.cc = EmailAddress.EMPTY_ARRAY;
+        return _this();
+    }
+
+    // ---------------------------------------------------------------- message
+
+    /**
+     * Sets message subject.
+     *
+     * @param subject The message subject to set.
+     * @return this
+     */
+    public T subject(final String subject) {
+        this.subject = subject;
+        return _this();
+    }
+
+    /**
+     * Sets message subject with specified encoding to override default platform encoding.
+     * If the subject contains non US-ASCII characters, it will be encoded using the specified charset.
+     * If the subject contains only US-ASCII characters, no encoding is done and it is used as-is.
+     * The application must ensure that the subject does not contain any line breaks.
+     *
+     * @param subject  The message subject
+     * @param encoding The encoding for the message subject.
+     * @return this
+     */
+    public T subject(final String subject, final String encoding) {
+        subject(subject);
+        this.subjectEncoding = encoding;
+        return _this();
+    }
+
+    /**
+     * Returns message subject.
+     *
+     * @return message subject.
+     */
+    public String subject() {
+        return this.subject;
+    }
+
+    /**
+     * Returns the message subject encoding.
+     *
+     * @return the message subject encoding.
+     */
+    public String subjectEncoding() {
+        return this.subjectEncoding;
+    }
+
+    /**
+     * Returns all messages.
+     */
+    public List<EmailMessage> messages() {
+        return messages;
+    }
+
+    /**
+     * Adds multiple messages.
+     *
+     * @param msgsToAdd {@link List} of {@link EmailMessage}s to add.
+     * @return this
+     */
+    public T message(final List<EmailMessage> msgsToAdd) {
+        messages.addAll(msgsToAdd);
+        return _this();
+    }
+
+    /**
+     * Adds an {@link EmailMessage}.
+     *
+     * @param msgToAdd {@link EmailMessage} to add.
+     * @return this
+     */
+    public T message(final EmailMessage msgToAdd) {
+        messages.add(msgToAdd);
+        return _this();
+    }
+
+    /**
+     * Adds a {@link EmailMessage}.
+     *
+     * @param text     The text to add as a {@link String}.
+     * @param mimeType The MIME type as a {@link String}.
+     * @param encoding The encoding as a {@link String}.
+     * @return this
+     * @see #message(EmailMessage)
+     */
+    public T message(final String text, final String mimeType, final String encoding) {
+        return message(new EmailMessage(text, mimeType, encoding));
+    }
+
+    /**
+     * Adds a {@link EmailMessage}.
+     *
+     * @param text     The text to add as a {@link String}.
+     * @param mimeType The MIME type as a {@link String}.
+     * @return this
+     * @see #message(EmailMessage)
+     */
+    public T message(final String text, final String mimeType) {
+        return message(new EmailMessage(text, mimeType));
+    }
+
+    /**
+     * Adds plain message text.
+     *
+     * @param text The text to add as a {@link String}.
+     * @return this
+     * @see #message(String, String)
+     */
+    public T textMessage(final String text) {
+        return message(text, MimeTypes.MIME_TEXT_PLAIN);
+    }
+
+    // ---------------------------------------------------------------- headers
+
+    /**
+     * Adds plain message text.
+     *
+     * @param text     The text to add as a {@link String}.
+     * @param encoding The encoding as a {@link String}.
+     * @return this
+     * @see #message(EmailMessage)
+     */
+    public T textMessage(final String text, final String encoding) {
+        return message(new EmailMessage(text, MimeTypes.MIME_TEXT_PLAIN, encoding));
+    }
+
+    /**
+     * Adds HTML message.
+     *
+     * @param html The HTML to add as a {@link String}.
+     * @return this
+     * @see #message(EmailMessage)
+     */
+    public T htmlMessage(final String html) {
+        return message(new EmailMessage(html, MimeTypes.MIME_TEXT_HTML));
+    }
+
+    /**
+     * Adds HTML message.
+     *
+     * @param html     The HTML to add as a {@link String}.
+     * @param encoding The encoding as a {@link String}.
+     * @return this
+     * @see #message(EmailMessage)
+     */
+    public T htmlMessage(final String html, final String encoding) {
+        return message(new EmailMessage(html, MimeTypes.MIME_TEXT_HTML, encoding));
+    }
+
+    /**
+     * Returns all headers as a {@link HashMap}.
+     *
+     * @return all headers in a {@link HashMap}
+     */
+    protected Map<String, String> headers() {
+        return headers;
+    }
+
+    /**
+     * Sets header value.
+     *
+     * @param name  The name of the header.
+     * @param value The value of the header.
+     * @return this
+     */
+    public T header(final String name, final String value) {
+        headers.put(name, value);
+        return _this();
+    }
+
+    /**
+     * Sets headers.
+     *
+     * @param headersToSet Headers to set.
+     * @return this
+     */
+    public T headers(final Map<String, String> headersToSet) {
+        headers.putAll(headersToSet);
+        return _this();
+    }
+
+    /**
+     * Sets headers.
+     *
+     * @param headersToSet Headers to set.
+     * @return this
+     * @see #header(String, String)
+     */
+    public T headers(final Enumeration<Header> headersToSet) {
+        while (headersToSet.hasMoreElements()) {
+            final Header header = headersToSet.nextElement();
+            header(header.getName(), header.getValue());
+        }
+        return _this();
+    }
+
+    /**
+     * Returns the value of a header.
+     *
+     * @param name The name of the header.
+     * @return The value of the header.
+     */
+    public String header(final String name) {
+        return headers.get(name);
+    }
+
+    // ---------------------------------------------------------------- attachments
+
+    /**
+     * Sets email priority.
+     *
+     * @param priority - Values of 1 through 5 are acceptable, with 1 being the highest priority, 3 = normal
+     *                 and 5 = lowest priority.
+     */
+    public T priority(final int priority) {
+        header(X_PRIORITY, String.valueOf(priority));
+        return _this();
+    }
+
+    /**
+     * Returns emails priority (1 - 5) or <code>-1</code> if priority not available.
+     *
+     * @see #priority(int)
+     */
+    public int priority() {
+        try {
+            return Integer.parseInt(headers.get(X_PRIORITY));
+        } catch (final NumberFormatException ignore) {
+            return -1;
+        }
+    }
+
+    /**
+     * Returns the list of all {@link EmailAttachment}s.
+     *
+     * @return List of {@link EmailAttachment}s. Returns empty list if no attachment is available.
+     */
+    public List<EmailAttachment<? extends DataSource>> attachments() {
+        return attachments;
+    }
+
+    /**
+     * Adds {@link EmailAttachment}s.
+     *
+     * @param attachments {@link List} of {@link EmailAttachment}s to add.
+     * @return this
+     */
+    protected T storeAttachments(final List<EmailAttachment<? extends DataSource>> attachments) {
+        this.attachments.addAll(attachments);
+        return _this();
+    }
+
+    /**
+     * Adds {@link EmailAttachment}.
+     *
+     * @param attachment {@link EmailAttachment} to add.
+     * @return this
+     */
+    protected T storeAttachment(final EmailAttachment<? extends DataSource> attachment) {
+        this.attachments.add(attachment);
+        return _this();
+    }
+
+    /**
+     * Adds {@link EmailAttachment}s.
+     *
+     * @param attachments {@link List} of {@link EmailAttachment}s to add.
+     * @return this
+     */
+    public T attachments(final List<EmailAttachment<? extends DataSource>> attachments) {
+        for (final EmailAttachment<?> attachment : attachments) {
+            attachment(attachment);
+        }
+        return _this();
+    }
+
+    /**
+     * Adds {@link EmailAttachment}. Content ID will be set to {@code null}.
+     *
+     * @param attachment {@link EmailAttachment} to add.
+     * @return this
+     */
+    public T attachment(final EmailAttachment<? extends DataSource> attachment) {
+        attachment.setContentId(null);
+        return storeAttachment(attachment);
+    }
+
+    /**
+     * @see #attachment(EmailAttachment)
+     */
+    public T attachment(final EmailAttachmentBuilder builder) {
+        return attachment(builder.buildByteArrayDataSource());
+    }
+
+    /**
+     * Attaches the embedded attachment: Content ID will be set if missing from attachment's file name.
+     *
+     * @param builder {@link EmailAttachmentBuilder}
+     * @return this
+     * @see #embeddedAttachment(EmailAttachment)
+     */
+    public T embeddedAttachment(final EmailAttachmentBuilder builder) {
+        builder.setContentIdFromNameIfMissing();
+
+        // https://github.com/oblac/jodd/issues/546
+        // https://github.com/oblac/jodd/issues/404#issuecomment-297011351
+        // content disposition will be set to "inline"
+        builder.inline(true);
+
+        return embeddedAttachment(builder.buildByteArrayDataSource());
+    }
+
+    // ---------------------------------------------------------------- date
+
+    /**
+     * Embed {@link EmailAttachment} to last message. No header is changed.
+     *
+     * @param attachment {@link EmailAttachment}
+     * @return this
+     * @see #storeAttachment(EmailAttachment)
+     */
+    public T embeddedAttachment(final EmailAttachment<? extends DataSource> attachment) {
+        storeAttachment(attachment);
+
+        final List<EmailMessage> messages = messages();
+        final int size = messages.size();
+        if (size > 1) {
+            // Add to last message
+            final int lastMessagePos = size - 1;
+            final EmailMessage lastMessage = messages.get(lastMessagePos);
+            attachment.setEmbeddedMessage(lastMessage);
+        }
+
+        return _this();
+    }
+
+    /**
+     * Sets email's sent date.
+     *
+     * @param date - Email's sent date. If {@code null}, then date will be set during the process of sending.
+     * @return this
+     */
+    public T sentDate(final Date date) {
+        sentDate = date;
+        return _this();
+    }
+
+
+    /**
+     * Returns email's sent date. If return value is {@code null}, then date
+     * will be set during the process of sending.
+     *
+     * @return email's sent date or {@code null} if it will be set later.
+     */
+    public Date sentDate() {
+        return sentDate;
+    }
+
+    // ---------------------------------------------------------------- toString
+
+    @Override
+    public String toString() {
+        return "Email{'" + from() + "\', subject='" + subject() + "\'}";
+    }
+
+    // ---------------------------------------------------------------- helper
+
+    protected EmailAddress[] valueOrEmptyArray(EmailAddress[] arr) {
+        if (arr == null) {
+            arr = EmailAddress.EMPTY_ARRAY;
+        }
+        return arr;
+    }
 }

+ 94 - 99
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EMLComposer.java

@@ -25,12 +25,7 @@
 
 package com.fjhx.utils.mail;
 
-import jakarta.mail.Address;
-import jakarta.mail.Message;
-import jakarta.mail.MessagingException;
-import jakarta.mail.Session;
-import jakarta.mail.Transport;
-import jakarta.mail.URLName;
+import jakarta.mail.*;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -38,97 +33,97 @@ import java.io.OutputStream;
 
 public class EMLComposer extends EMLProperties<EMLComposer> {
 
-	public static EMLComposer create() {
-		return new EMLComposer();
-	}
-
-	/**
-	 * Creates EML string from given {@link Email}.
-	 *
-	 * @param email {@link Email} from which to create EML {@link String}.
-	 * @return {@link String} with EML content.
-	 */
-	public String compose(final Email email) {
-		if (getSession() == null) {
-			createSession(getProperties());
-		}
-
-		final OutputStreamTransport ost = new OutputStreamTransport(getSession());
-
-		final SendMailSession sendMailSession = new SendMailSession(getSession(), ost);
-
-		sendMailSession.sendMail(email);
-
-		return ost.getEml();
-	}
-
-
-	/**
-	 * Creates EML string from given {@link ReceivedEmail}.
-	 *
-	 * @param receivedEmail {@link ReceivedEmail} from which to create EML {@link String}.
-	 * @return {@link String} with EML content.
-	 */
-	public String compose(final ReceivedEmail receivedEmail) {
-		Message msg = receivedEmail.originalMessage();
-
-		final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-
-		try {
-			msg.writeTo(outputStream);
-		} catch (IOException | MessagingException e) {
-			throw new MailException(e);
-		}
-
-		return outputStream.toString();
-	}
-
-
-	/**
-	 * Special transport that writes message into the {@link OutputStream}.
-	 */
-	private static class OutputStreamTransport extends Transport {
-
-		/**
-		 * Creates a new {@link OutputStreamTransport}.
-		 *
-		 * @param session {@link Session}.
-		 */
-		public OutputStreamTransport(final Session session) {
-			super(session, new URLName("JODD_MAIL_2_EML", null, -1, null, null, null));
-		}
-
-		/**
-		 * Sends a message.
-		 *
-		 * @param msg       {@link Message} to send.
-		 * @param addresses array of {@link Address}es to send to.
-		 */
-		@Override
-		public void sendMessage(final Message msg, final Address[] addresses) {
-			final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-
-			try {
-				msg.writeTo(outputStream);
-			} catch (IOException | MessagingException e) {
-				throw new MailException(e);
-			}
-
-			eml = outputStream.toString();
-		}
-
-		/**
-		 * Returns the EML content.
-		 *
-		 * @return EML content.
-		 */
-		public String getEml() {
-			return eml;
-		}
-
-		/**
-		 * String with EML content.
-		 */
-		private String eml;
-	}
+    public static EMLComposer create() {
+        return new EMLComposer();
+    }
+
+    /**
+     * Creates EML string from given {@link Email}.
+     *
+     * @param email {@link Email} from which to create EML {@link String}.
+     * @return {@link String} with EML content.
+     */
+    public String compose(final Email email) {
+        if (getSession() == null) {
+            createSession(getProperties());
+        }
+
+        final OutputStreamTransport ost = new OutputStreamTransport(getSession());
+
+        final SendMailSession sendMailSession = new SendMailSession(getSession(), ost);
+
+        sendMailSession.sendMail(email);
+
+        return ost.getEml();
+    }
+
+
+    /**
+     * Creates EML string from given {@link ReceivedEmail}.
+     *
+     * @param receivedEmail {@link ReceivedEmail} from which to create EML {@link String}.
+     * @return {@link String} with EML content.
+     */
+    public String compose(final ReceivedEmail receivedEmail) {
+        Message msg = receivedEmail.originalMessage();
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        try {
+            msg.writeTo(outputStream);
+        } catch (IOException | MessagingException e) {
+            throw new MailException(e);
+        }
+
+        return outputStream.toString();
+    }
+
+
+    /**
+     * Special transport that writes message into the {@link OutputStream}.
+     */
+    private static class OutputStreamTransport extends Transport {
+
+        /**
+         * String with EML content.
+         */
+        private String eml;
+
+        /**
+         * Creates a new {@link OutputStreamTransport}.
+         *
+         * @param session {@link Session}.
+         */
+        public OutputStreamTransport(final Session session) {
+            super(session, new URLName("JODD_MAIL_2_EML", null, -1, null, null, null));
+        }
+
+        /**
+         * Sends a message.
+         *
+         * @param msg       {@link Message} to send.
+         * @param addresses array of {@link Address}es to send to.
+         */
+        @Override
+        public void sendMessage(final Message msg, final Address[] addresses) {
+            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+            try {
+                msg.writeTo(outputStream);
+            } catch (IOException | MessagingException e) {
+                throw new MailException(e);
+            }
+
+            eml = outputStream.toString();
+        }
+
+        /**
+         * Returns the EML content.
+         *
+         * @return EML content.
+         */
+        public String getEml() {
+            return eml;
+        }
+    }
 }

+ 80 - 85
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EMLParser.java

@@ -30,102 +30,97 @@ import jakarta.mail.Session;
 import jakarta.mail.internet.MimeMessage;
 import jodd.io.IOUtil;
 
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
 
 /**
  * Developer-friendly class for parsing EML files.
  */
 public class EMLParser extends EMLProperties<EMLParser> {
 
-	public static EMLParser create() {
-		return new EMLParser();
-	}
+    public static EMLParser create() {
+        return new EMLParser();
+    }
 
-	/**
-	 * Parses EML with provided EML content.
-	 *
-	 * @param emlContent {@link String} with EML content.
-	 * @param charset    String with charset.
-	 * @return {@link ReceivedEmail}.
-	 * @throws UnsupportedEncodingException if the named charset is not supported.
-	 * @throws MessagingException           if {@link MimeMessage} cannot be created.
-	 * @see #parse(byte[])
-	 */
-	public ReceivedEmail parse(final String emlContent, final String charset) throws
-			UnsupportedEncodingException, MessagingException {
+    /**
+     * Parses EML with provided EML content.
+     *
+     * @param emlContent {@link String} with EML content.
+     * @param charset    String with charset.
+     * @return {@link ReceivedEmail}.
+     * @throws UnsupportedEncodingException if the named charset is not supported.
+     * @throws MessagingException           if {@link MimeMessage} cannot be created.
+     * @see #parse(byte[])
+     */
+    public ReceivedEmail parse(final String emlContent, final String charset) throws
+            UnsupportedEncodingException, MessagingException {
 
-		final byte[] bytes = emlContent.getBytes(charset);
-		return parse(bytes);
-	}
+        final byte[] bytes = emlContent.getBytes(charset);
+        return parse(bytes);
+    }
 
-	/**
-	 * Parses EML with provided EML content.
-	 *
-	 * @param emlContent {@link String} with EML content.
-	 * @return {@link ReceivedEmail}.
-	 * @throws MessagingException if {@link MimeMessage} cannot be created.
-	 * @see #parse(String, String)
-	 */
-	public ReceivedEmail parse(final String emlContent) throws MessagingException {
-		try {
-			return parse(emlContent, "UTF-8");
-		} catch (final UnsupportedEncodingException ignore) {
-			return null;
-		}
-	}
+    /**
+     * Parses EML with provided EML content.
+     *
+     * @param emlContent {@link String} with EML content.
+     * @return {@link ReceivedEmail}.
+     * @throws MessagingException if {@link MimeMessage} cannot be created.
+     * @see #parse(String, String)
+     */
+    public ReceivedEmail parse(final String emlContent) throws MessagingException {
+        try {
+            return parse(emlContent, "UTF-8");
+        } catch (final UnsupportedEncodingException ignore) {
+            return null;
+        }
+    }
 
-	/**
-	 * Parses EML with provided EML content.
-	 *
-	 * @param content byte[] with EML content.
-	 * @return {@link ReceivedEmail}.
-	 * @throws MessagingException if {@link MimeMessage} cannot be created.
-	 * @see #parse(InputStream)
-	 */
-	public ReceivedEmail parse(final byte[] content) throws MessagingException {
-		return parse(new ByteArrayInputStream(content));
-	}
+    /**
+     * Parses EML with provided EML content.
+     *
+     * @param content byte[] with EML content.
+     * @return {@link ReceivedEmail}.
+     * @throws MessagingException if {@link MimeMessage} cannot be created.
+     * @see #parse(InputStream)
+     */
+    public ReceivedEmail parse(final byte[] content) throws MessagingException {
+        return parse(new ByteArrayInputStream(content));
+    }
 
-	/**
-	 * Starts EML parsing with provided EML {@link File}.
-	 *
-	 * @param emlFile {@link File} with EML content.
-	 * @return {@link ReceivedEmail}.
-	 * @throws FileNotFoundException if emlFile cannot be found
-	 * @throws MessagingException    if {@link MimeMessage} cannot be created.
-	 * @see #parse(InputStream)
-	 */
-	public ReceivedEmail parse(final File emlFile) throws FileNotFoundException, MessagingException {
-		final FileInputStream fileInputStream = new FileInputStream(emlFile);
-		try {
-			return parse(fileInputStream);
-		} finally {
-			IOUtil.close(fileInputStream);
-		}
-	}
+    /**
+     * Starts EML parsing with provided EML {@link File}.
+     *
+     * @param emlFile {@link File} with EML content.
+     * @return {@link ReceivedEmail}.
+     * @throws FileNotFoundException if emlFile cannot be found
+     * @throws MessagingException    if {@link MimeMessage} cannot be created.
+     * @see #parse(InputStream)
+     */
+    public ReceivedEmail parse(final File emlFile) throws FileNotFoundException, MessagingException {
+        final FileInputStream fileInputStream = new FileInputStream(emlFile);
+        try {
+            return parse(fileInputStream);
+        } finally {
+            IOUtil.close(fileInputStream);
+        }
+    }
 
-	/**
-	 * Parses the EML content. If {@link Session} is not created, default one will be used.
-	 *
-	 * @param emlContentInputStream {@link InputStream} containing the EML content.
-	 * @return {@link ReceivedEmail}.
-	 * @throws MessagingException if {@link MimeMessage} cannot be created.
-	 */
-	protected ReceivedEmail parse(final InputStream emlContentInputStream) throws MessagingException {
-		if (getSession() == null) {
-			createSession(getProperties());
-		}
+    /**
+     * Parses the EML content. If {@link Session} is not created, default one will be used.
+     *
+     * @param emlContentInputStream {@link InputStream} containing the EML content.
+     * @return {@link ReceivedEmail}.
+     * @throws MessagingException if {@link MimeMessage} cannot be created.
+     */
+    protected ReceivedEmail parse(final InputStream emlContentInputStream) throws MessagingException {
+        if (getSession() == null) {
+            createSession(getProperties());
+        }
 
-		try {
-			final MimeMessage message = new MimeMessage(getSession(), emlContentInputStream);
-			return new ReceivedEmail(message, false, null);
-		} finally {
-			IOUtil.close(emlContentInputStream);
-		}
-	}
+        try {
+            final MimeMessage message = new MimeMessage(getSession(), emlContentInputStream);
+            return new ReceivedEmail(message, false, null);
+        } finally {
+            IOUtil.close(emlContentInputStream);
+        }
+    }
 }

+ 97 - 98
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EMLProperties.java

@@ -30,103 +30,102 @@ import java.util.Properties;
 
 abstract class EMLProperties<T extends EMLProperties<T>> {
 
-	@SuppressWarnings("unchecked")
-	protected T _this() {
-		return (T) this;
-	}
-
-	/**
-	 * {@link Properties}.
-	 */
-	private Session session;
-
-	/**
-	 * Returns the {@link Session}.
-	 *
-	 * @return the {@link Session}.
-	 */
-	protected Session getSession() {
-		return session;
-	}
-
-	/**
-	 * Custom {@link Properties}.
-	 */
-	private final Properties properties = new Properties();
-
-	/**
-	 * Returns the {@link Properties}.
-	 *
-	 * @return the {@link Properties}.
-	 */
-	protected Properties getProperties() {
-		return properties;
-	}
-
-	/**
-	 * Creates new {@link Session} with or without custom {@link Properties}.
-	 *
-	 * @param properties Custom properties to be used during {@link Session} creation. It is acceptable is this value is {@code null}.
-	 * @return {@link Session} with any custom {@link Properties}
-	 */
-	protected Session createSession(Properties properties) {
-		if (properties == null) {
-			properties = System.getProperties();
-		}
-
-		this.session = Session.getInstance(properties);
-
-		return session;
-	}
-
-	/**
-	 * Copies properties from given set. If {@link Session} is already created, exception will be thrown.
-	 *
-	 * @param properties {@link Properties} to set.
-	 * @return this
-	 * @throws MailException if the {@link Properties} has already been set.
-	 */
-	public T set(final Properties properties) throws MailException {
-		checkSessionNotSet();
-		this.properties.putAll(properties);
-		return _this();
-	}
-
-	/**
-	 * Sets property for the {@link Session}. If {@link Session} is already created, an exception
-	 * will be thrown.
-	 *
-	 * @param name  Property name to set.
-	 * @param value Property value to set.
-	 * @return this
-	 * @throws MailException if the {@link Properties} has already been set.
-	 */
-	public T set(final String name, final String value) {
-		checkSessionNotSet();
-		properties.setProperty(name, value);
-		return _this();
-	}
-
-	/**
-	 * Uses default {@link Session}. Any property will be ignored.
-	 *
-	 * @return this
-	 * @see System#getProperties()
-	 */
-	public T useDefaultSession() {
-		this.session = Session.getDefaultInstance(System.getProperties());
-		return _this();
-	}
-
-	/**
-	 * Ensures that {@link Session} has not yet been set.
-	 *
-	 * @throws MailException if {@link Session} has already been set.
-	 */
-	private void checkSessionNotSet() throws MailException {
-		if (session != null) {
-			throw new MailException("Can't set properties after session is assigned");
-		}
-	}
+    /**
+     * Custom {@link Properties}.
+     */
+    private final Properties properties = new Properties();
+    /**
+     * {@link Properties}.
+     */
+    private Session session;
+
+    @SuppressWarnings("unchecked")
+    protected T _this() {
+        return (T) this;
+    }
+
+    /**
+     * Returns the {@link Session}.
+     *
+     * @return the {@link Session}.
+     */
+    protected Session getSession() {
+        return session;
+    }
+
+    /**
+     * Returns the {@link Properties}.
+     *
+     * @return the {@link Properties}.
+     */
+    protected Properties getProperties() {
+        return properties;
+    }
+
+    /**
+     * Creates new {@link Session} with or without custom {@link Properties}.
+     *
+     * @param properties Custom properties to be used during {@link Session} creation. It is acceptable is this value is {@code null}.
+     * @return {@link Session} with any custom {@link Properties}
+     */
+    protected Session createSession(Properties properties) {
+        if (properties == null) {
+            properties = System.getProperties();
+        }
+
+        this.session = Session.getInstance(properties);
+
+        return session;
+    }
+
+    /**
+     * Copies properties from given set. If {@link Session} is already created, exception will be thrown.
+     *
+     * @param properties {@link Properties} to set.
+     * @return this
+     * @throws MailException if the {@link Properties} has already been set.
+     */
+    public T set(final Properties properties) throws MailException {
+        checkSessionNotSet();
+        this.properties.putAll(properties);
+        return _this();
+    }
+
+    /**
+     * Sets property for the {@link Session}. If {@link Session} is already created, an exception
+     * will be thrown.
+     *
+     * @param name  Property name to set.
+     * @param value Property value to set.
+     * @return this
+     * @throws MailException if the {@link Properties} has already been set.
+     */
+    public T set(final String name, final String value) {
+        checkSessionNotSet();
+        properties.setProperty(name, value);
+        return _this();
+    }
+
+    /**
+     * Uses default {@link Session}. Any property will be ignored.
+     *
+     * @return this
+     * @see System#getProperties()
+     */
+    public T useDefaultSession() {
+        this.session = Session.getDefaultInstance(System.getProperties());
+        return _this();
+    }
+
+    /**
+     * Ensures that {@link Session} has not yet been set.
+     *
+     * @throws MailException if {@link Session} has already been set.
+     */
+    private void checkSessionNotSet() throws MailException {
+        if (session != null) {
+            throw new MailException("Can't set properties after session is assigned");
+        }
+    }
 
 }

+ 145 - 145
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/Email.java

@@ -35,151 +35,151 @@ import java.util.Date;
  */
 public class Email extends CommonEmail<Email> {
 
-	/**
-	 * Static constructor for fluent interface.
-	 */
-	public static Email create() {
-		return new Email();
-	}
-
-	@Override
-	public Email clone() {
-		return create()
-
-			// from / reply-to
-			.from(from())
-			.replyTo(replyTo())
-
-			// recipients
-			.to(to())
-			.cc(cc())
-			.bcc(bcc())
-
-			// subject
-			.subject(subject(), subjectEncoding())
-
-			// dates
-			.sentDate(sentDate())
-
-			// headers - includes priority
-			.headers(headers())
-
-			// content / attachments
-			.storeAttachments(attachments())
-			.message(messages());
-	}
-
-	// ---------------------------------------------------------------- date
-
-	/**
-	 * Sets current date as the sent date.
-	 *
-	 * @return this
-	 * @see #sentDate(Date)
-	 */
-	public Email currentSentDate() {
-		return sentDate(new Date());
-	}
-
-	// ---------------------------------------------------------------- bcc
-
-	/**
-	 * BCC address.
-	 */
-	private EmailAddress[] bcc = EmailAddress.EMPTY_ARRAY;
-
-	/**
-	 * Appends BCC address.
-	 *
-	 * @param to {@link EmailAddress} to add.
-	 * @return this
-	 */
-	public Email bcc(final EmailAddress to) {
-		this.bcc = ArraysUtil.append(this.bcc, to);
-		return _this();
-	}
-
-	/**
-	 * Appends BCC address.
-	 *
-	 * @param bcc Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
-	 * @return this
-	 * @see #bcc(EmailAddress)
-	 */
-	public Email bcc(final String bcc) {
-		return bcc(EmailAddress.of(bcc));
-	}
-
-	/**
-	 * Appends BCC address.
-	 *
-	 * @param personalName personal name.
-	 * @param bcc          email address.
-	 * @return this
-	 * @see #bcc(EmailAddress)
-	 */
-	public Email bcc(final String personalName, final String bcc) {
-		return bcc(new EmailAddress(personalName, bcc));
-	}
-
-	/**
-	 * Appends BCC address.
-	 *
-	 * @param bcc {@link Address} to add.
-	 * @return this
-	 * @see #bcc(EmailAddress)
-	 */
-	public Email bcc(final Address bcc) {
-		return bcc(EmailAddress.of(bcc));
-	}
-
-	/**
-	 * Appends BCC address.
-	 *
-	 * @param bccs array of {@link String}s to set.
-	 * @return this
-	 * @see #bcc(EmailAddress...)
-	 */
-	public Email bcc(final String... bccs) {
-		return bcc(EmailAddress.of(bccs));
-	}
-
-	/**
-	 * Appends BCC address.
-	 *
-	 * @param bccs array of {@link Address}es to set.
-	 * @return this
-	 * @see #bcc(EmailAddress...)
-	 */
-	public Email bcc(final Address... bccs) {
-		return bcc(EmailAddress.of(bccs));
-	}
-
-	/**
-	 * Appends one or more BCC addresses.
-	 *
-	 * @param bccs vararg of {@link EmailAddress}es to set.
-	 * @return this
-	 */
-	public Email bcc(final EmailAddress... bccs) {
-		this.bcc = ArraysUtil.join(this.bcc, valueOrEmptyArray(bccs));
-		return _this();
-	}
-
-	/**
-	 * Returns BCC addresses.
-	 */
-	public EmailAddress[] bcc() {
-		return bcc;
-	}
-
-	/**
-	 * Resets BCC addresses.
-	 */
-	public Email resetBcc() {
-		this.bcc = EmailAddress.EMPTY_ARRAY;
-		return _this();
-	}
+    /**
+     * BCC address.
+     */
+    private EmailAddress[] bcc = EmailAddress.EMPTY_ARRAY;
+
+    /**
+     * Static constructor for fluent interface.
+     */
+    public static Email create() {
+        return new Email();
+    }
+
+    // ---------------------------------------------------------------- date
+
+    @Override
+    public Email clone() {
+        return create()
+
+                // from / reply-to
+                .from(from())
+                .replyTo(replyTo())
+
+                // recipients
+                .to(to())
+                .cc(cc())
+                .bcc(bcc())
+
+                // subject
+                .subject(subject(), subjectEncoding())
+
+                // dates
+                .sentDate(sentDate())
+
+                // headers - includes priority
+                .headers(headers())
+
+                // content / attachments
+                .storeAttachments(attachments())
+                .message(messages());
+    }
+
+    // ---------------------------------------------------------------- bcc
+
+    /**
+     * Sets current date as the sent date.
+     *
+     * @return this
+     * @see #sentDate(Date)
+     */
+    public Email currentSentDate() {
+        return sentDate(new Date());
+    }
+
+    /**
+     * Appends BCC address.
+     *
+     * @param to {@link EmailAddress} to add.
+     * @return this
+     */
+    public Email bcc(final EmailAddress to) {
+        this.bcc = ArraysUtil.append(this.bcc, to);
+        return _this();
+    }
+
+    /**
+     * Appends BCC address.
+     *
+     * @param bcc Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
+     * @return this
+     * @see #bcc(EmailAddress)
+     */
+    public Email bcc(final String bcc) {
+        return bcc(EmailAddress.of(bcc));
+    }
+
+    /**
+     * Appends BCC address.
+     *
+     * @param personalName personal name.
+     * @param bcc          email address.
+     * @return this
+     * @see #bcc(EmailAddress)
+     */
+    public Email bcc(final String personalName, final String bcc) {
+        return bcc(new EmailAddress(personalName, bcc));
+    }
+
+    /**
+     * Appends BCC address.
+     *
+     * @param bcc {@link Address} to add.
+     * @return this
+     * @see #bcc(EmailAddress)
+     */
+    public Email bcc(final Address bcc) {
+        return bcc(EmailAddress.of(bcc));
+    }
+
+    /**
+     * Appends BCC address.
+     *
+     * @param bccs array of {@link String}s to set.
+     * @return this
+     * @see #bcc(EmailAddress...)
+     */
+    public Email bcc(final String... bccs) {
+        return bcc(EmailAddress.of(bccs));
+    }
+
+    /**
+     * Appends BCC address.
+     *
+     * @param bccs array of {@link Address}es to set.
+     * @return this
+     * @see #bcc(EmailAddress...)
+     */
+    public Email bcc(final Address... bccs) {
+        return bcc(EmailAddress.of(bccs));
+    }
+
+    /**
+     * Appends one or more BCC addresses.
+     *
+     * @param bccs vararg of {@link EmailAddress}es to set.
+     * @return this
+     */
+    public Email bcc(final EmailAddress... bccs) {
+        this.bcc = ArraysUtil.join(this.bcc, valueOrEmptyArray(bccs));
+        return _this();
+    }
+
+    /**
+     * Returns BCC addresses.
+     */
+    public EmailAddress[] bcc() {
+        return bcc;
+    }
+
+    /**
+     * Resets BCC addresses.
+     */
+    public Email resetBcc() {
+        this.bcc = EmailAddress.EMPTY_ARRAY;
+        return _this();
+    }
 
 
 }

+ 193 - 193
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailAddress.java

@@ -25,12 +25,12 @@
 
 package com.fjhx.utils.mail;
 
-import jodd.util.StringUtil;
-
 import jakarta.mail.Address;
 import jakarta.mail.MessagingException;
 import jakarta.mail.internet.AddressException;
 import jakarta.mail.internet.InternetAddress;
+import jodd.util.StringUtil;
+
 import java.io.UnsupportedEncodingException;
 
 /**
@@ -39,195 +39,195 @@ import java.io.UnsupportedEncodingException;
  */
 public class EmailAddress {
 
-	public static final EmailAddress[] EMPTY_ARRAY = new EmailAddress[0];
-
-	/**
-	 * Email address.
-	 */
-	private final String email;
-
-	/**
-	 * Personal name.
-	 */
-	private final String personalName;
-
-	/**
-	 * Creates new address by specifying email and personal name.
-	 *
-	 * @param personalName personal name.
-	 * @param email        email address.
-	 */
-	public EmailAddress(final String personalName, final String email) {
-		this.email = email;
-		this.personalName = personalName;
-	}
-
-	/**
-	 * @see #EmailAddress(String, String)
-	 */
-	public static EmailAddress of(final String personalName, final String email) {
-		return new EmailAddress(personalName, email);
-	}
-
-	/**
-	 * Creates new address by specifying one of the following:
-	 * <ul>
-	 * <li>{@code "foo@bar.com" - only email address.}</li>
-	 * <li>{@code "Jenny Doe &lt;foo@bar.com&gt;" - first part of the string is personal name,
-	 * and the other part is email, surrounded with < and >.}</li>
-	 * </ul>
-	 *
-	 * @param address {@link String} containing address to convert.
-	 */
-	public static EmailAddress of(String address) {
-		address = address.trim();
-
-		if (!StringUtil.endsWithChar(address, '>')) {
-			return new EmailAddress(null, address);
-		}
-
-		final int ndx = address.lastIndexOf('<');
-		if (ndx == -1) {
-			return new EmailAddress(null, address);
-		}
-
-		String email = address.substring(ndx + 1, address.length() - 1);
-		String personalName = address.substring(0, ndx).trim();
-		return new EmailAddress(personalName, email);
-	}
-
-	/**
-	 * Creates new email address from {@link InternetAddress}.
-	 *
-	 * @param internetAddress {@link InternetAddress} to convert
-	 */
-	public static EmailAddress of(final InternetAddress internetAddress) {
-		return new EmailAddress(internetAddress.getPersonal(), internetAddress.getAddress());
-	}
-
-	/**
-	 * Creates new email address from {@link InternetAddress}.
-	 *
-	 * @param address {@link Address} to convert.
-	 */
-	public static EmailAddress of(final Address address) {
-		return of(address.toString());
-	}
-
-	// ---------------------------------------------------------------- getters
-
-	/**
-	 * Returns email address.
-	 *
-	 * @return email address.
-	 */
-	public String getEmail() {
-		return email;
-	}
-
-	/**
-	 * Returns personal name.
-	 *
-	 * @return personal name. Value may be {@code null}.
-	 */
-	public String getPersonalName() {
-		return personalName;
-	}
-
-	/**
-	 * Returns string representation of this.
-	 *
-	 * @return String representation of this.
-	 */
-	@Override
-	public String toString() {
-		if (this.personalName == null) {
-			return this.email;
-		}
-		return this.personalName + " <" + this.email + '>';
-	}
-
-	// ---------------------------------------------------------------- convert
-
-	/**
-	 * Creates new {@link InternetAddress} from current data.
-	 *
-	 * @return {@link InternetAddress} from current data.
-	 */
-	public InternetAddress toInternetAddress() throws AddressException {
-		try {
-			return new InternetAddress(email, personalName, "UTF-8");
-		} catch (final UnsupportedEncodingException ueex) {
-			throw new AddressException(ueex.toString());
-		}
-	}
-
-	// ---------------------------------------------------------------- arrays
-
-	/**
-	 * Converts array of {@link Address} to {@link EmailAddress}.
-	 *
-	 * @param addresses array of {@link Address}es to convert.
-	 * @return an array of {@link EmailAddress}.
-	 */
-	public static EmailAddress[] of(final Address... addresses) {
-		if (addresses == null) {
-			return EmailAddress.EMPTY_ARRAY;
-		}
-		if (addresses.length == 0) {
-			return EmailAddress.EMPTY_ARRAY;
-		}
-
-		final EmailAddress[] res = new EmailAddress[addresses.length];
-
-		for (int i = 0; i < addresses.length; i++) {
-			res[i] = EmailAddress.of(addresses[i]);
-		}
-
-		return res;
-	}
-
-	/**
-	 * Converts array of {@link String} to {@link EmailAddress}.
-	 *
-	 * @param addresses array of {@link String}s to convert.
-	 * @return an array of {@link EmailAddress}.
-	 */
-	public static EmailAddress[] of(final String... addresses) {
-		if (addresses == null) {
-			return EmailAddress.EMPTY_ARRAY;
-		}
-		if (addresses.length == 0) {
-			return EmailAddress.EMPTY_ARRAY;
-		}
-
-		final EmailAddress[] res = new EmailAddress[addresses.length];
-
-		for (int i = 0; i < addresses.length; i++) {
-			res[i] = EmailAddress.of(addresses[i]);
-		}
-
-		return res;
-	}
-
-	/**
-	 * Convert from array of {@link EmailAddress} to array of {@link InternetAddress}.
-	 *
-	 * @param addresses {@link EmailMessage}
-	 * @return array of {@link InternetAddress}. Returns empty array if addresses was {@code null}.
-	 * @throws MessagingException if there are failures
-	 */
-	public static InternetAddress[] convert(final EmailAddress[] addresses) throws MessagingException {
-		if (addresses == null) {
-			return new InternetAddress[0];
-		}
-
-		final int numRecipients = addresses.length;
-		final InternetAddress[] address = new InternetAddress[numRecipients];
-
-		for (int i = 0; i < numRecipients; i++) {
-			address[i] = addresses[i].toInternetAddress();
-		}
-		return address;
-	}
+    public static final EmailAddress[] EMPTY_ARRAY = new EmailAddress[0];
+
+    /**
+     * Email address.
+     */
+    private final String email;
+
+    /**
+     * Personal name.
+     */
+    private final String personalName;
+
+    /**
+     * Creates new address by specifying email and personal name.
+     *
+     * @param personalName personal name.
+     * @param email        email address.
+     */
+    public EmailAddress(final String personalName, final String email) {
+        this.email = email;
+        this.personalName = personalName;
+    }
+
+    /**
+     * @see #EmailAddress(String, String)
+     */
+    public static EmailAddress of(final String personalName, final String email) {
+        return new EmailAddress(personalName, email);
+    }
+
+    /**
+     * Creates new address by specifying one of the following:
+     * <ul>
+     * <li>{@code "foo@bar.com" - only email address.}</li>
+     * <li>{@code "Jenny Doe &lt;foo@bar.com&gt;" - first part of the string is personal name,
+     * and the other part is email, surrounded with < and >.}</li>
+     * </ul>
+     *
+     * @param address {@link String} containing address to convert.
+     */
+    public static EmailAddress of(String address) {
+        address = address.trim();
+
+        if (!StringUtil.endsWithChar(address, '>')) {
+            return new EmailAddress(null, address);
+        }
+
+        final int ndx = address.lastIndexOf('<');
+        if (ndx == -1) {
+            return new EmailAddress(null, address);
+        }
+
+        String email = address.substring(ndx + 1, address.length() - 1);
+        String personalName = address.substring(0, ndx).trim();
+        return new EmailAddress(personalName, email);
+    }
+
+    /**
+     * Creates new email address from {@link InternetAddress}.
+     *
+     * @param internetAddress {@link InternetAddress} to convert
+     */
+    public static EmailAddress of(final InternetAddress internetAddress) {
+        return new EmailAddress(internetAddress.getPersonal(), internetAddress.getAddress());
+    }
+
+    /**
+     * Creates new email address from {@link InternetAddress}.
+     *
+     * @param address {@link Address} to convert.
+     */
+    public static EmailAddress of(final Address address) {
+        return of(address.toString());
+    }
+
+    // ---------------------------------------------------------------- getters
+
+    /**
+     * Converts array of {@link Address} to {@link EmailAddress}.
+     *
+     * @param addresses array of {@link Address}es to convert.
+     * @return an array of {@link EmailAddress}.
+     */
+    public static EmailAddress[] of(final Address... addresses) {
+        if (addresses == null) {
+            return EmailAddress.EMPTY_ARRAY;
+        }
+        if (addresses.length == 0) {
+            return EmailAddress.EMPTY_ARRAY;
+        }
+
+        final EmailAddress[] res = new EmailAddress[addresses.length];
+
+        for (int i = 0; i < addresses.length; i++) {
+            res[i] = EmailAddress.of(addresses[i]);
+        }
+
+        return res;
+    }
+
+    /**
+     * Converts array of {@link String} to {@link EmailAddress}.
+     *
+     * @param addresses array of {@link String}s to convert.
+     * @return an array of {@link EmailAddress}.
+     */
+    public static EmailAddress[] of(final String... addresses) {
+        if (addresses == null) {
+            return EmailAddress.EMPTY_ARRAY;
+        }
+        if (addresses.length == 0) {
+            return EmailAddress.EMPTY_ARRAY;
+        }
+
+        final EmailAddress[] res = new EmailAddress[addresses.length];
+
+        for (int i = 0; i < addresses.length; i++) {
+            res[i] = EmailAddress.of(addresses[i]);
+        }
+
+        return res;
+    }
+
+    /**
+     * Convert from array of {@link EmailAddress} to array of {@link InternetAddress}.
+     *
+     * @param addresses {@link EmailMessage}
+     * @return array of {@link InternetAddress}. Returns empty array if addresses was {@code null}.
+     * @throws MessagingException if there are failures
+     */
+    public static InternetAddress[] convert(final EmailAddress[] addresses) throws MessagingException {
+        if (addresses == null) {
+            return new InternetAddress[0];
+        }
+
+        final int numRecipients = addresses.length;
+        final InternetAddress[] address = new InternetAddress[numRecipients];
+
+        for (int i = 0; i < numRecipients; i++) {
+            address[i] = addresses[i].toInternetAddress();
+        }
+        return address;
+    }
+
+    // ---------------------------------------------------------------- convert
+
+    /**
+     * Returns email address.
+     *
+     * @return email address.
+     */
+    public String getEmail() {
+        return email;
+    }
+
+    // ---------------------------------------------------------------- arrays
+
+    /**
+     * Returns personal name.
+     *
+     * @return personal name. Value may be {@code null}.
+     */
+    public String getPersonalName() {
+        return personalName;
+    }
+
+    /**
+     * Returns string representation of this.
+     *
+     * @return String representation of this.
+     */
+    @Override
+    public String toString() {
+        if (this.personalName == null) {
+            return this.email;
+        }
+        return this.personalName + " <" + this.email + '>';
+    }
+
+    /**
+     * Creates new {@link InternetAddress} from current data.
+     *
+     * @return {@link InternetAddress} from current data.
+     */
+    public InternetAddress toInternetAddress() throws AddressException {
+        try {
+            return new InternetAddress(email, personalName, "UTF-8");
+        } catch (final UnsupportedEncodingException ueex) {
+            throw new AddressException(ueex.toString());
+        }
+    }
 }

+ 257 - 272
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailAttachment.java

@@ -29,283 +29,268 @@ import jakarta.activation.DataSource;
 import jakarta.mail.internet.MimeUtility;
 import jodd.io.IOUtil;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
 
 /**
  * Email attachment.
  */
 public class EmailAttachment<T extends DataSource> {
 
-	/**
-	 * {@link String} with file name.
-	 */
-	private final String name;
-
-	/**
-	 * Content ID of attachment.
-	 */
-	private String contentId;
-
-	/**
-	 * Whether the attachment is inline.
-	 */
-	private boolean isInline;
-
-	/**
-	 * {@link DataSource} of the attachment.
-	 */
-	private final T dataSource;
-
-	/**
-	 * Target {@link EmailMessage}.
-	 */
-	private EmailMessage targetMessage;
-
-	// ---------------------------------------------------------------- constructor
-
-	/**
-	 * Returns new/empty {@link EmailAttachmentBuilder}.
-	 *
-	 * @return {@link EmailAttachmentBuilder}.
-	 */
-	public static EmailAttachmentBuilder with() {
-		return new EmailAttachmentBuilder();
-	}
-
-	/**
-	 * Creates new attachment with given name and content id for inline attachments.
-	 *
-	 * @param contentId Value may be {@code null} if attachment is not embedded.
-	 * @param isInline  {@code true} if the attachment is inline.
-	 * @param name      Email name may be {@code null} as well.
-	 * @see MimeUtility#decodeText(String)
-	 */
-	protected EmailAttachment(final String name, final String contentId, final boolean isInline, final T dataSource) {
-		if (name != null) {
-			try {
-				this.name = MimeUtility.decodeText(name);
-			} catch (final UnsupportedEncodingException useexc) {
-				throw new MailException(useexc);
-			}
-		} else {
-			this.name = null;
-		}
-		this.contentId = contentId;
-		this.isInline = isInline;
-		this.dataSource = dataSource;
-	}
-
-	// ---------------------------------------------------------------- properties
-
-	/**
-	 * Returns attachment name.
-	 *
-	 * @return attachment name. Value may be {@code null}.
-	 */
-	public String getName() {
-		return name;
-	}
-
-	/**
-	 * Returns encoded attachment name.
-	 *
-	 * @return encoded attachment name. Value may be {@code null}.
-	 */
-	public String getEncodedName() {
-		if (name == null) {
-			return null;
-		}
-		try {
-			return MimeUtility.encodeText(name);
-		} catch (final UnsupportedEncodingException ueex) {
-			throw new MailException(ueex);
-		}
-	}
-
-	/**
-	 * Returns content id for inline attachments.
-	 * <p>
-	 * Value is {@code null} when attachment is not embedded.
-	 *
-	 * @return content id for inline attachments
-	 * @see #isEmbedded()
-	 */
-	public String getContentId() {
-		return contentId;
-	}
-
-	/**
-	 * Returns {@code true} if the attachment is embedded.
-	 * <p>
-	 * Embedded attachment is one when {@link #getContentId()} is not {@code null}.
-	 *
-	 * @return {@code true} if the attachment is embedded.
-	 */
-	public boolean isEmbedded() {
-		return contentId != null;
-	}
-
-	/**
-	 * Returns {@code true} if it is an inline attachment.
-	 *
-	 * @return {@code true} if it is an inline attachment.
-	 */
-	public boolean isInline() {
-		return isInline;
-	}
-
-	/**
-	 * Sets whether attachment is inline.
-	 *
-	 * @param isInline {@code true} for inline.
-	 * @return this
-	 */
-	protected EmailAttachment<T> setInline(final boolean isInline) {
-		this.isInline = isInline;
-		return this;
-	}
-
-	/**
-	 * Sets content ID.
-	 *
-	 * @param contentId content ID of {@link EmailAttachment}.
-	 * @return this
-	 */
-	protected EmailAttachment<T> setContentId(final String contentId) {
-		this.contentId = contentId;
-		return this;
-	}
-
-	/**
-	 * Sets target message for embedded attachments.
-	 *
-	 * @param emailMessage target {@link EmailMessage}.
-	 */
-	public EmailAttachment<T> setEmbeddedMessage(final EmailMessage emailMessage) {
-		targetMessage = emailMessage;
-		return this;
-	}
-
-	/**
-	 * Returns {@code true} if attachment is embedded into provided message.
-	 *
-	 * @param emailMessage target {@link EmailMessage}.
-	 * @return {@code true} if attachment is embedded into provided message.
-	 */
-	public boolean isEmbeddedInto(final EmailMessage emailMessage) {
-		return targetMessage == emailMessage;
-	}
-
-	// ---------------------------------------------------------------- data source
-
-	/**
-	 * Returns {@link DataSource} implementation, depending on attachment source.
-	 */
-	public T getDataSource() {
-		return dataSource;
-	}
-
-	/**
-	 * Returns content type of {@link DataSource}.
-	 *
-	 * @return content type of {@link DataSource}.
-	 */
-	public String getContentType() {
-		return dataSource.getContentType();
-	}
-
-	// ---------------------------------------------------------------- size
-
-	/**
-	 * Size of attachment. Defaults to -1.
-	 */
-	private int size = -1;
-
-	/**
-	 * Returns size of attachment.
-	 *
-	 * @return size of attachment or -1 if not yet calculated from {@link DataSource}.
-	 */
-	public int getSize() {
-		return size;
-	}
-
-	/**
-	 * Sets size of attachment.
-	 *
-	 * @param size the size of the attachment.
-	 * @return this
-	 */
-	protected EmailAttachment<T> setSize(final int size) {
-		this.size = size;
-		return this;
-	}
-
-	// ---------------------------------------------------------------- content methods
-
-	/**
-	 * Returns byte content of the attachment.
-	 *
-	 * @return byte array with content of the attachment.
-	 */
-	public byte[] toByteArray() {
-		final ByteArrayOutputStream out;
-		if (size != -1) {
-			out = new ByteArrayOutputStream(size);
-		} else {
-			out = new ByteArrayOutputStream();
-		}
-		writeToStream(out);
-		return out.toByteArray();
-	}
-
-	/**
-	 * Saves attachment to a file.
-	 *
-	 * @param destination The destination file to be written.
-	 */
-	public void writeToFile(final File destination) {
-		InputStream input = null;
-		OutputStream output = null;
-		try {
-			input = getDataSource().getInputStream();
-			output = new FileOutputStream(destination);
-
-			IOUtil.copy(input, output);
-		}
-		catch (final IOException ioex) {
-			throw new MailException(ioex);
-		}
-		finally {
-			IOUtil.close(input);
-			IOUtil.close(output);
-		}
-	}
-
-	/**
-	 * Saves attachment to the output stream.
-	 *
-	 * @param out OutputStream where attachment should be copied to.
-	 */
-	public void writeToStream(final OutputStream out) {
-		InputStream input = null;
-		try {
-			input = getDataSource().getInputStream();
-
-			IOUtil.copy(input, out);
-		}
-		catch (final IOException ioex) {
-			throw new MailException(ioex);
-		}
-		finally {
-			IOUtil.close(input);
-		}
-	}
+    /**
+     * {@link String} with file name.
+     */
+    private final String name;
+    /**
+     * {@link DataSource} of the attachment.
+     */
+    private final T dataSource;
+    /**
+     * Content ID of attachment.
+     */
+    private String contentId;
+    /**
+     * Whether the attachment is inline.
+     */
+    private boolean isInline;
+    /**
+     * Target {@link EmailMessage}.
+     */
+    private EmailMessage targetMessage;
+
+    // ---------------------------------------------------------------- constructor
+    /**
+     * Size of attachment. Defaults to -1.
+     */
+    private int size = -1;
+
+    /**
+     * Creates new attachment with given name and content id for inline attachments.
+     *
+     * @param contentId Value may be {@code null} if attachment is not embedded.
+     * @param isInline  {@code true} if the attachment is inline.
+     * @param name      Email name may be {@code null} as well.
+     * @see MimeUtility#decodeText(String)
+     */
+    protected EmailAttachment(final String name, final String contentId, final boolean isInline, final T dataSource) {
+        if (name != null) {
+            try {
+                this.name = MimeUtility.decodeText(name);
+            } catch (final UnsupportedEncodingException useexc) {
+                throw new MailException(useexc);
+            }
+        } else {
+            this.name = null;
+        }
+        this.contentId = contentId;
+        this.isInline = isInline;
+        this.dataSource = dataSource;
+    }
+
+    // ---------------------------------------------------------------- properties
+
+    /**
+     * Returns new/empty {@link EmailAttachmentBuilder}.
+     *
+     * @return {@link EmailAttachmentBuilder}.
+     */
+    public static EmailAttachmentBuilder with() {
+        return new EmailAttachmentBuilder();
+    }
+
+    /**
+     * Returns attachment name.
+     *
+     * @return attachment name. Value may be {@code null}.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns encoded attachment name.
+     *
+     * @return encoded attachment name. Value may be {@code null}.
+     */
+    public String getEncodedName() {
+        if (name == null) {
+            return null;
+        }
+        try {
+            return MimeUtility.encodeText(name);
+        } catch (final UnsupportedEncodingException ueex) {
+            throw new MailException(ueex);
+        }
+    }
+
+    /**
+     * Returns content id for inline attachments.
+     * <p>
+     * Value is {@code null} when attachment is not embedded.
+     *
+     * @return content id for inline attachments
+     * @see #isEmbedded()
+     */
+    public String getContentId() {
+        return contentId;
+    }
+
+    /**
+     * Sets content ID.
+     *
+     * @param contentId content ID of {@link EmailAttachment}.
+     * @return this
+     */
+    protected EmailAttachment<T> setContentId(final String contentId) {
+        this.contentId = contentId;
+        return this;
+    }
+
+    /**
+     * Returns {@code true} if the attachment is embedded.
+     * <p>
+     * Embedded attachment is one when {@link #getContentId()} is not {@code null}.
+     *
+     * @return {@code true} if the attachment is embedded.
+     */
+    public boolean isEmbedded() {
+        return contentId != null;
+    }
+
+    /**
+     * Returns {@code true} if it is an inline attachment.
+     *
+     * @return {@code true} if it is an inline attachment.
+     */
+    public boolean isInline() {
+        return isInline;
+    }
+
+    /**
+     * Sets whether attachment is inline.
+     *
+     * @param isInline {@code true} for inline.
+     * @return this
+     */
+    protected EmailAttachment<T> setInline(final boolean isInline) {
+        this.isInline = isInline;
+        return this;
+    }
+
+    /**
+     * Sets target message for embedded attachments.
+     *
+     * @param emailMessage target {@link EmailMessage}.
+     */
+    public EmailAttachment<T> setEmbeddedMessage(final EmailMessage emailMessage) {
+        targetMessage = emailMessage;
+        return this;
+    }
+
+    // ---------------------------------------------------------------- data source
+
+    /**
+     * Returns {@code true} if attachment is embedded into provided message.
+     *
+     * @param emailMessage target {@link EmailMessage}.
+     * @return {@code true} if attachment is embedded into provided message.
+     */
+    public boolean isEmbeddedInto(final EmailMessage emailMessage) {
+        return targetMessage == emailMessage;
+    }
+
+    /**
+     * Returns {@link DataSource} implementation, depending on attachment source.
+     */
+    public T getDataSource() {
+        return dataSource;
+    }
+
+    // ---------------------------------------------------------------- size
+
+    /**
+     * Returns content type of {@link DataSource}.
+     *
+     * @return content type of {@link DataSource}.
+     */
+    public String getContentType() {
+        return dataSource.getContentType();
+    }
+
+    /**
+     * Returns size of attachment.
+     *
+     * @return size of attachment or -1 if not yet calculated from {@link DataSource}.
+     */
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * Sets size of attachment.
+     *
+     * @param size the size of the attachment.
+     * @return this
+     */
+    protected EmailAttachment<T> setSize(final int size) {
+        this.size = size;
+        return this;
+    }
+
+    // ---------------------------------------------------------------- content methods
+
+    /**
+     * Returns byte content of the attachment.
+     *
+     * @return byte array with content of the attachment.
+     */
+    public byte[] toByteArray() {
+        final ByteArrayOutputStream out;
+        if (size != -1) {
+            out = new ByteArrayOutputStream(size);
+        } else {
+            out = new ByteArrayOutputStream();
+        }
+        writeToStream(out);
+        return out.toByteArray();
+    }
+
+    /**
+     * Saves attachment to a file.
+     *
+     * @param destination The destination file to be written.
+     */
+    public void writeToFile(final File destination) {
+        InputStream input = null;
+        OutputStream output = null;
+        try {
+            input = getDataSource().getInputStream();
+            output = new FileOutputStream(destination);
+
+            IOUtil.copy(input, output);
+        } catch (final IOException ioex) {
+            throw new MailException(ioex);
+        } finally {
+            IOUtil.close(input);
+            IOUtil.close(output);
+        }
+    }
+
+    /**
+     * Saves attachment to the output stream.
+     *
+     * @param out OutputStream where attachment should be copied to.
+     */
+    public void writeToStream(final OutputStream out) {
+        InputStream input = null;
+        try {
+            input = getDataSource().getInputStream();
+
+            IOUtil.copy(input, out);
+        } catch (final IOException ioex) {
+            throw new MailException(ioex);
+        } finally {
+            IOUtil.close(input);
+        }
+    }
 
 }

+ 242 - 246
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailAttachmentBuilder.java

@@ -25,13 +25,13 @@
 
 package com.fjhx.utils.mail;
 
+import jakarta.activation.DataSource;
+import jakarta.activation.FileDataSource;
 import jakarta.mail.util.ByteArrayDataSource;
 import jodd.io.FileNameUtil;
 import jodd.io.FileUtil;
 import jodd.net.MimeTypes;
 
-import jakarta.activation.DataSource;
-import jakarta.activation.FileDataSource;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -44,249 +44,245 @@ import static com.fjhx.utils.mail.EmailUtil.NO_NAME;
  */
 public class EmailAttachmentBuilder {
 
-	// ---------------------------------------------------------------- constructor
-
-	/**
-	 * Only allow instantiation from {@link EmailAttachment} class
-	 */
-	protected EmailAttachmentBuilder() {
-	}
-
-	// ---------------------------------------------------------------- properties
-
-	/**
-	 * {@link String} with name of {@link EmailAttachment}.
-	 */
-	private String name;
-
-	/**
-	 * Content ID of {@link EmailAttachment}.
-	 */
-	private String contentId;
-
-	/**
-	 * Whether the {@link EmailAttachment} is inline. Defaults to false.
-	 */
-	private boolean isInline = false;
-
-	/**
-	 * {@link DataSource} containing {@link EmailAttachment} content.
-	 */
-	private DataSource dataSource;
-
-	/**
-	 * Target {@link EmailMessage}.
-	 */
-	private EmailMessage targetMessage;
-
-	// ---------------------------------------------------------------- data
-
-	/**
-	 * Sets file name.
-	 *
-	 * @param name File name to set.
-	 * @return this
-	 */
-	public EmailAttachmentBuilder name(final String name) {
-		if (name != null && !name.trim().isEmpty()) {
-			this.name = name;
-		}
-		return this;
-	}
-
-	/**
-	 * Sets content ID.
-	 *
-	 * @param contentId content ID of {@link EmailAttachment}.
-	 * @return this
-	 */
-	public EmailAttachmentBuilder contentId(final String contentId) {
-		this.contentId = contentId;
-		return this;
-	}
-
-	/**
-	 * Sets whether {@link EmailAttachment} is inline.
-	 *
-	 * @param isInline {@code true} for inline.
-	 * @return this
-	 */
-	public EmailAttachmentBuilder inline(final boolean isInline) {
-		this.isInline = isInline;
-		return this;
-	}
-
-	/**
-	 * Sets target {@link EmailMessage}.
-	 *
-	 * @param targetMessage Target {@link EmailMessage}.
-	 * @return this
-	 */
-	public EmailAttachmentBuilder embeddedMessage(final EmailMessage targetMessage) {
-		this.targetMessage = targetMessage;
-		return this;
-	}
-
-	/**
-	 * Sets the {@link DataSource}. Common {@link DataSource}s include {@link ByteArrayDataSource} and
-	 * {@link FileDataSource}.
-	 *
-	 * @param dataSource {@link DataSource}
-	 * @return this
-	 */
-	public <T extends DataSource> EmailAttachmentBuilder content(final T dataSource) {
-		this.dataSource = dataSource;
-		name(dataSource.getName());
-		return this;
-	}
-
-	/**
-	 * Creates new {@link ByteArrayDataSource} and then calls {@link #content(DataSource)}.
-	 *
-	 * @param inputStream {@link InputStream}
-	 * @param contentType content type from {@link EmailAttachment}.
-	 * @return this
-	 * @throws IOException if {@link ByteArrayDataSource} cannot be created from {@link InputStream}
-	 * @see #content(DataSource)
-	 */
-	public EmailAttachmentBuilder content(final InputStream inputStream, final String contentType)
-		throws IOException {
-		return content(new ByteArrayDataSource(inputStream, resolveContentType(contentType)));
-	}
-
-	/**
-	 * Creates new {@link ByteArrayDataSource} and then calls {@link #content(DataSource)}.
-	 *
-	 * @param bytes       array of bytes
-	 * @param contentType content type from {@link EmailAttachment}.
-	 * @return this
-	 * @see #content(DataSource)
-	 */
-	public EmailAttachmentBuilder content(final byte[] bytes, final String contentType) {
-		return content(new ByteArrayDataSource(bytes, resolveContentType(contentType)));
-	}
-
-	/**
-	 * Uses {@code null} contentType.
-	 *
-	 * @see #content(byte[], String)
-	 */
-	public EmailAttachmentBuilder content(final byte[] bytes) {
-		return content(bytes, null);
-	}
-
-	/**
-	 * Creates new {@link FileDataSource} and then calls {@link #content(DataSource)}
-	 *
-	 * @param file {@link File}
-	 * @return this
-	 * @see #content(DataSource)
-	 */
-	public EmailAttachmentBuilder content(final File file) {
-		return content(new FileDataSource(file));
-	}
-
-	/**
-	 * @param fileName String representing file name.
-	 * @return this
-	 * @see #content(File)
-	 */
-	public EmailAttachmentBuilder content(final String fileName) {
-		return content(new File(fileName));
-	}
-
-	// ---------------------------------------------------------------- factory/builder
-
-	/**
-	 * Creates {@link EmailAttachment}.
-	 *
-	 * @return {@link EmailAttachment}.
-	 * @throws MailException if issue with {@link DataSource}.
-	 */
-	public EmailAttachment<ByteArrayDataSource> buildByteArrayDataSource() throws MailException {
-		try {
-			final ByteArrayDataSource bads;
-			if (dataSource instanceof ByteArrayDataSource) {
-				bads = (ByteArrayDataSource) dataSource;
-			} else {
-				bads = new ByteArrayDataSource(dataSource.getInputStream(), dataSource.getContentType());
-			}
-			checkDataSource();
-			return new EmailAttachment<>(name, contentId, isInline, bads).setEmbeddedMessage(targetMessage);
-		} catch (final IOException ioexc) {
-			throw new MailException(ioexc);
-		}
-	}
-
-	/**
-	 * Creates {@link EmailAttachment}.
-	 *
-	 * @return {@link EmailAttachment}.
-	 * @throws MailException if issue with {@link DataSource}.
-	 */
-	public EmailAttachment<FileDataSource> buildFileDataSource(final String messageId, final File attachmentStorage) throws MailException {
-		try {
-			final FileDataSource fds;
-			if (dataSource instanceof FileDataSource) {
-				fds = (FileDataSource) dataSource;
-			} else {
-				final File file = new File(attachmentStorage, messageId);
-				FileUtil.writeStream(file, dataSource.getInputStream());
-				fds = new FileDataSource(file);
-			}
-			checkDataSource();
-			return new EmailAttachment<>(name, contentId, isInline, fds).setEmbeddedMessage(targetMessage);
-		} catch (final IOException ioexc) {
-			throw new MailException(ioexc);
-		}
-	}
-
-	/**
-	 * Check to ensure {@link DataSource} ds is valid.
-	 *
-	 * @throws MailException if DataSource is {@code null}.
-	 */
-	private void checkDataSource() {
-		if (dataSource == null) {
-			throw new MailException("dataSource must be valid. It can be set using #content().");
-		}
-	}
-
-	// ---------------------------------------------------------------- properties
-
-	/**
-	 * Set content ID if it is missing.
-	 *
-	 * @return this
-	 * @see #contentId(String)
-	 */
-	protected EmailAttachmentBuilder setContentIdFromNameIfMissing() {
-		if (contentId == null) {
-			if (name != null) {
-				contentId(FileNameUtil.getName(name));
-			} else {
-				contentId(NO_NAME);
-			}
-		}
-		return this;
-	}
-
-	/**
-	 * Resolves content type from all data.
-	 *
-	 * @param contentType Content type if we know it. {@code null} is fine to use.
-	 * @return content type
-	 */
-	protected String resolveContentType(final String contentType) {
-		if (contentType != null) {
-			return contentType;
-		}
-		if (name == null) {
-			return MimeTypes.MIME_APPLICATION_OCTET_STREAM;
-		}
-
-		final String extension = FileNameUtil.getExtension(name);
-		return MimeTypes.getMimeType(extension);
-	}
+    // ---------------------------------------------------------------- constructor
+
+    /**
+     * {@link String} with name of {@link EmailAttachment}.
+     */
+    private String name;
+
+    // ---------------------------------------------------------------- properties
+    /**
+     * Content ID of {@link EmailAttachment}.
+     */
+    private String contentId;
+    /**
+     * Whether the {@link EmailAttachment} is inline. Defaults to false.
+     */
+    private boolean isInline = false;
+    /**
+     * {@link DataSource} containing {@link EmailAttachment} content.
+     */
+    private DataSource dataSource;
+    /**
+     * Target {@link EmailMessage}.
+     */
+    private EmailMessage targetMessage;
+
+    /**
+     * Only allow instantiation from {@link EmailAttachment} class
+     */
+    protected EmailAttachmentBuilder() {
+    }
+
+    // ---------------------------------------------------------------- data
+
+    /**
+     * Sets file name.
+     *
+     * @param name File name to set.
+     * @return this
+     */
+    public EmailAttachmentBuilder name(final String name) {
+        if (name != null && !name.trim().isEmpty()) {
+            this.name = name;
+        }
+        return this;
+    }
+
+    /**
+     * Sets content ID.
+     *
+     * @param contentId content ID of {@link EmailAttachment}.
+     * @return this
+     */
+    public EmailAttachmentBuilder contentId(final String contentId) {
+        this.contentId = contentId;
+        return this;
+    }
+
+    /**
+     * Sets whether {@link EmailAttachment} is inline.
+     *
+     * @param isInline {@code true} for inline.
+     * @return this
+     */
+    public EmailAttachmentBuilder inline(final boolean isInline) {
+        this.isInline = isInline;
+        return this;
+    }
+
+    /**
+     * Sets target {@link EmailMessage}.
+     *
+     * @param targetMessage Target {@link EmailMessage}.
+     * @return this
+     */
+    public EmailAttachmentBuilder embeddedMessage(final EmailMessage targetMessage) {
+        this.targetMessage = targetMessage;
+        return this;
+    }
+
+    /**
+     * Sets the {@link DataSource}. Common {@link DataSource}s include {@link ByteArrayDataSource} and
+     * {@link FileDataSource}.
+     *
+     * @param dataSource {@link DataSource}
+     * @return this
+     */
+    public <T extends DataSource> EmailAttachmentBuilder content(final T dataSource) {
+        this.dataSource = dataSource;
+        name(dataSource.getName());
+        return this;
+    }
+
+    /**
+     * Creates new {@link ByteArrayDataSource} and then calls {@link #content(DataSource)}.
+     *
+     * @param inputStream {@link InputStream}
+     * @param contentType content type from {@link EmailAttachment}.
+     * @return this
+     * @throws IOException if {@link ByteArrayDataSource} cannot be created from {@link InputStream}
+     * @see #content(DataSource)
+     */
+    public EmailAttachmentBuilder content(final InputStream inputStream, final String contentType)
+            throws IOException {
+        return content(new ByteArrayDataSource(inputStream, resolveContentType(contentType)));
+    }
+
+    /**
+     * Creates new {@link ByteArrayDataSource} and then calls {@link #content(DataSource)}.
+     *
+     * @param bytes       array of bytes
+     * @param contentType content type from {@link EmailAttachment}.
+     * @return this
+     * @see #content(DataSource)
+     */
+    public EmailAttachmentBuilder content(final byte[] bytes, final String contentType) {
+        return content(new ByteArrayDataSource(bytes, resolveContentType(contentType)));
+    }
+
+    /**
+     * Uses {@code null} contentType.
+     *
+     * @see #content(byte[], String)
+     */
+    public EmailAttachmentBuilder content(final byte[] bytes) {
+        return content(bytes, null);
+    }
+
+    /**
+     * Creates new {@link FileDataSource} and then calls {@link #content(DataSource)}
+     *
+     * @param file {@link File}
+     * @return this
+     * @see #content(DataSource)
+     */
+    public EmailAttachmentBuilder content(final File file) {
+        return content(new FileDataSource(file));
+    }
+
+    /**
+     * @param fileName String representing file name.
+     * @return this
+     * @see #content(File)
+     */
+    public EmailAttachmentBuilder content(final String fileName) {
+        return content(new File(fileName));
+    }
+
+    // ---------------------------------------------------------------- factory/builder
+
+    /**
+     * Creates {@link EmailAttachment}.
+     *
+     * @return {@link EmailAttachment}.
+     * @throws MailException if issue with {@link DataSource}.
+     */
+    public EmailAttachment<ByteArrayDataSource> buildByteArrayDataSource() throws MailException {
+        try {
+            final ByteArrayDataSource bads;
+            if (dataSource instanceof ByteArrayDataSource) {
+                bads = (ByteArrayDataSource) dataSource;
+            } else {
+                bads = new ByteArrayDataSource(dataSource.getInputStream(), dataSource.getContentType());
+            }
+            checkDataSource();
+            return new EmailAttachment<>(name, contentId, isInline, bads).setEmbeddedMessage(targetMessage);
+        } catch (final IOException ioexc) {
+            throw new MailException(ioexc);
+        }
+    }
+
+    /**
+     * Creates {@link EmailAttachment}.
+     *
+     * @return {@link EmailAttachment}.
+     * @throws MailException if issue with {@link DataSource}.
+     */
+    public EmailAttachment<FileDataSource> buildFileDataSource(final String messageId, final File attachmentStorage) throws MailException {
+        try {
+            final FileDataSource fds;
+            if (dataSource instanceof FileDataSource) {
+                fds = (FileDataSource) dataSource;
+            } else {
+                final File file = new File(attachmentStorage, messageId);
+                FileUtil.writeStream(file, dataSource.getInputStream());
+                fds = new FileDataSource(file);
+            }
+            checkDataSource();
+            return new EmailAttachment<>(name, contentId, isInline, fds).setEmbeddedMessage(targetMessage);
+        } catch (final IOException ioexc) {
+            throw new MailException(ioexc);
+        }
+    }
+
+    /**
+     * Check to ensure {@link DataSource} ds is valid.
+     *
+     * @throws MailException if DataSource is {@code null}.
+     */
+    private void checkDataSource() {
+        if (dataSource == null) {
+            throw new MailException("dataSource must be valid. It can be set using #content().");
+        }
+    }
+
+    // ---------------------------------------------------------------- properties
+
+    /**
+     * Set content ID if it is missing.
+     *
+     * @return this
+     * @see #contentId(String)
+     */
+    protected EmailAttachmentBuilder setContentIdFromNameIfMissing() {
+        if (contentId == null) {
+            if (name != null) {
+                contentId(FileNameUtil.getName(name));
+            } else {
+                contentId(NO_NAME);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Resolves content type from all data.
+     *
+     * @param contentType Content type if we know it. {@code null} is fine to use.
+     * @return content type
+     */
+    protected String resolveContentType(final String contentType) {
+        if (contentType != null) {
+            return contentType;
+        }
+        if (name == null) {
+            return MimeTypes.MIME_APPLICATION_OCTET_STREAM;
+        }
+
+        final String extension = FileNameUtil.getExtension(name);
+        return MimeTypes.getMimeType(extension);
+    }
 
 }

+ 343 - 358
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailFilter.java

@@ -28,21 +28,7 @@ package com.fjhx.utils.mail;
 import jakarta.mail.Flags;
 import jakarta.mail.Header;
 import jakarta.mail.Message;
-import jakarta.mail.search.AndTerm;
-import jakarta.mail.search.BodyTerm;
-import jakarta.mail.search.FlagTerm;
-import jakarta.mail.search.FromStringTerm;
-import jakarta.mail.search.HeaderTerm;
-import jakarta.mail.search.MessageIDTerm;
-import jakarta.mail.search.MessageNumberTerm;
-import jakarta.mail.search.NotTerm;
-import jakarta.mail.search.OrTerm;
-import jakarta.mail.search.ReceivedDateTerm;
-import jakarta.mail.search.RecipientStringTerm;
-import jakarta.mail.search.SearchTerm;
-import jakarta.mail.search.SentDateTerm;
-import jakarta.mail.search.SizeTerm;
-import jakarta.mail.search.SubjectTerm;
+import jakarta.mail.search.*;
 
 import java.util.Date;
 
@@ -63,348 +49,347 @@ import java.util.Date;
  */
 public class EmailFilter {
 
-	boolean operatorAnd = true;
-	boolean nextIsNot;
-
-	/**
-	 * Creates new Email filter.
-	 */
-	public static EmailFilter filter() {
-		return new EmailFilter();
-	}
-
-	/**
-	 * The {@link SearchTerm} to be used.
-	 */
-	protected SearchTerm searchTerm;
-
-	/**
-	 * Defines filter for SUBJECT field.
-	 *
-	 * @param subject The SUBJECT.
-	 * @return this
-	 */
-	public EmailFilter subject(final String subject) {
-		final SearchTerm subjectTerm = new SubjectTerm(subject);
-		concat(subjectTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filter for message id.
-	 *
-	 * @param messageId The message ID.
-	 * @return this
-	 */
-	public EmailFilter messageId(final String messageId) {
-		final SearchTerm msgIdTerm = new MessageIDTerm(messageId);
-		concat(msgIdTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filteer for message number.
-	 *
-	 * @param messageNumber The message number.
-	 * @return this
-	 */
-	public EmailFilter messageNumber(final int messageNumber) {
-		final SearchTerm msgIdTerm = new MessageNumberTerm(messageNumber);
-		concat(msgIdTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filter for FROM field.
-	 *
-	 * @param fromAddress The FROM address
-	 * @return this
-	 */
-	public EmailFilter from(final String fromAddress) {
-		final SearchTerm fromTerm = new FromStringTerm(fromAddress);
-		concat(fromTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filter for TO field.
-	 *
-	 * @param toAddress The TO address.
-	 * @return this
-	 */
-	public EmailFilter to(final String toAddress) {
-		final SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.TO, toAddress);
-		concat(toTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filter for CC field.
-	 *
-	 * @param ccAddress CC addreses.
-	 * @return this
-	 */
-	public EmailFilter cc(final String ccAddress) {
-		final SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.CC, ccAddress);
-		concat(toTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filter for BCC field.
-	 *
-	 * @param bccAddress BCC address.
-	 * @return this
-	 */
-	public EmailFilter bcc(final String bccAddress) {
-		final SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.BCC, bccAddress);
-		concat(toTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filter for many flags at once.
-	 *
-	 * @param flags The {@link Flags} to filter on.
-	 * @param value The flag setting to check for.
-	 * @return this
-	 */
-	public EmailFilter flags(final Flags flags, final boolean value) {
-		final SearchTerm flagTerm = new FlagTerm(flags, value);
-		concat(flagTerm);
-		return this;
-	}
-
-	/**
-	 * Defines filter for single flag.
-	 *
-	 * @param flag  The flag to filter on.
-	 * @param value The flag setting to check for.
-	 * @return this
-	 */
-	public EmailFilter flag(final Flags.Flag flag, final boolean value) {
-		final Flags flags = new Flags();
-		flags.add(flag);
-		return flags(flags, value);
-	}
-
-	/**
-	 * Defines filter for received date.
-	 *
-	 * @return this
-	 */
-	public EmailFilter receivedDate(final Operator operator, final long milliseconds) {
-		final SearchTerm term = new ReceivedDateTerm(operator.value, new Date(milliseconds));
-		concat(term);
-		return this;
-	}
-
-	/**
-	 * Defines filter for sent date.
-	 *
-	 * @param operator     {@link Operator} to use.
-	 * @param milliseconds the milliseconds since January 1, 1970, 00:00:00 GMT.
-	 * @return this
-	 */
-	public EmailFilter sentDate(final Operator operator, final long milliseconds) {
-		final SearchTerm term = new SentDateTerm(operator.value, new Date(milliseconds));
-		concat(term);
-		return this;
-	}
-
-	/**
-	 * Defines filter on a message body.
-	 * All parts of the message that are of MIME type "text/*" are searched.
-	 *
-	 * @param pattern String pattern use in body.
-	 * @return this
-	 */
-	public EmailFilter text(final String pattern) {
-		final SearchTerm term = new BodyTerm(pattern);
-		concat(term);
-		return this;
-	}
-
-	/**
-	 * Defines filter for {@link Header}.
-	 *
-	 * @param headerName The name of the {@link Header}.
-	 * @param pattern    String pattern to use for headerName.
-	 * @return this
-	 */
-	public EmailFilter header(final String headerName, final String pattern) {
-		final SearchTerm term = new HeaderTerm(headerName, pattern);
-		concat(term);
-		return this;
-	}
-
-	/**
-	 * Defines filter for message size.
-	 *
-	 * @param comparison {@link Operator}.
-	 * @param size       size of message.
-	 * @return this
-	 */
-	public EmailFilter size(final Operator comparison, final int size) {
-		final SearchTerm term = new SizeTerm(comparison.value, size);
-		concat(term);
-		return this;
-	}
-
-	/**
-	 * Comparison operator.
-	 */
-	public enum Operator {
-		EQ(jakarta.mail.search.ComparisonTerm.EQ),
-		GE(jakarta.mail.search.ComparisonTerm.GE),
-		GT(jakarta.mail.search.ComparisonTerm.GT),
-		LE(jakarta.mail.search.ComparisonTerm.LE),
-		LT(jakarta.mail.search.ComparisonTerm.LT),
-		NE(jakarta.mail.search.ComparisonTerm.NE);
-
-		private final int value;
-
-		Operator(final int value) {
-			this.value = value;
-		}
-	}
-
-	// ---------------------------------------------------------------- boolean
-
-	/**
-	 * Changes concatenation mode to AND.
-	 *
-	 * @return this
-	 */
-	public EmailFilter and() {
-		this.operatorAnd = true;
-		return this;
-	}
-
-	/**
-	 * Changes concatenation mode to OR.
-	 *
-	 * @return this
-	 */
-	public EmailFilter or() {
-		this.operatorAnd = false;
-		return this;
-	}
-
-	/**
-	 * Marks next condition to be NOT.
-	 *
-	 * @return this
-	 */
-	public EmailFilter not() {
-		this.nextIsNot = true;
-		return this;
-	}
-
-	/**
-	 * Defines AND group of filters.
-	 *
-	 * @param emailFilters array of {@link EmailFilter}s to AND.
-	 * @return this
-	 */
-	public EmailFilter and(final EmailFilter... emailFilters) {
-		final SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];
-
-		for (int i = 0; i < emailFilters.length; i++) {
-			searchTerms[i] = emailFilters[i].searchTerm;
-		}
-
-		concat(new AndTerm(searchTerms));
-		return this;
-	}
-
-	/**
-	 * Defines OR group of filters.
-	 *
-	 * @param emailFilters array of {@link EmailFilter}s to OR.
-	 * @return this
-	 */
-	public EmailFilter or(final EmailFilter... emailFilters) {
-		final SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];
-
-		for (int i = 0; i < emailFilters.length; i++) {
-			searchTerms[i] = emailFilters[i].searchTerm;
-		}
-
-		concat(new OrTerm(searchTerms));
-		return this;
-	}
-
-	/**
-	 * Appends single filter as NOT.
-	 *
-	 * @param emailFilter {@link EmailFilter} to append.
-	 * @return this
-	 */
-	public EmailFilter not(final EmailFilter emailFilter) {
-		final SearchTerm searchTerm = new NotTerm(emailFilter.searchTerm);
-		concat(searchTerm);
-		return this;
-	}
-
-	// ---------------------------------------------------------------- concat
-
-	/**
-	 * Concatenates last search term with new one.
-	 *
-	 * @param searchTerm searchTerm {@link SearchTerm} concatenate.
-	 * @see #and(SearchTerm)
-	 * @see #or(SearchTerm)
-	 */
-	protected void concat(SearchTerm searchTerm) {
-		if (nextIsNot) {
-			searchTerm = new NotTerm(searchTerm);
-			nextIsNot = false;
-		}
-		if (operatorAnd) {
-			and(searchTerm);
-		} else {
-			or(searchTerm);
-		}
-	}
-
-	/**
-	 * Sets {@link AndTerm} as searchTerm.
-	 *
-	 * @param searchTerm {@link SearchTerm} to set as AND.
-	 */
-	protected void and(final SearchTerm searchTerm) {
-		if (this.searchTerm == null) {
-			this.searchTerm = searchTerm;
-			return;
-		}
-
-		this.searchTerm = new AndTerm(this.searchTerm, searchTerm);
-	}
-
-	/**
-	 * Sets {@link OrTerm} searchTerm.
-	 *
-	 * @param searchTerm {@link SearchTerm} to set as OR.
-	 */
-	protected void or(final SearchTerm searchTerm) {
-		if (this.searchTerm == null) {
-			this.searchTerm = searchTerm;
-			return;
-		}
-
-		this.searchTerm = new OrTerm(this.searchTerm, searchTerm);
-	}
-
-	// ---------------------------------------------------------------- term
-
-	/**
-	 * Returns search term.
-	 *
-	 * @return {@link SearchTerm}.
-	 */
-	public SearchTerm getSearchTerm() {
-		return searchTerm;
-	}
+    /**
+     * The {@link SearchTerm} to be used.
+     */
+    protected SearchTerm searchTerm;
+    boolean operatorAnd = true;
+    boolean nextIsNot;
+
+    /**
+     * Creates new Email filter.
+     */
+    public static EmailFilter filter() {
+        return new EmailFilter();
+    }
+
+    /**
+     * Defines filter for SUBJECT field.
+     *
+     * @param subject The SUBJECT.
+     * @return this
+     */
+    public EmailFilter subject(final String subject) {
+        final SearchTerm subjectTerm = new SubjectTerm(subject);
+        concat(subjectTerm);
+        return this;
+    }
+
+    /**
+     * Defines filter for message id.
+     *
+     * @param messageId The message ID.
+     * @return this
+     */
+    public EmailFilter messageId(final String messageId) {
+        final SearchTerm msgIdTerm = new MessageIDTerm(messageId);
+        concat(msgIdTerm);
+        return this;
+    }
+
+    /**
+     * Defines filteer for message number.
+     *
+     * @param messageNumber The message number.
+     * @return this
+     */
+    public EmailFilter messageNumber(final int messageNumber) {
+        final SearchTerm msgIdTerm = new MessageNumberTerm(messageNumber);
+        concat(msgIdTerm);
+        return this;
+    }
+
+    /**
+     * Defines filter for FROM field.
+     *
+     * @param fromAddress The FROM address
+     * @return this
+     */
+    public EmailFilter from(final String fromAddress) {
+        final SearchTerm fromTerm = new FromStringTerm(fromAddress);
+        concat(fromTerm);
+        return this;
+    }
+
+    /**
+     * Defines filter for TO field.
+     *
+     * @param toAddress The TO address.
+     * @return this
+     */
+    public EmailFilter to(final String toAddress) {
+        final SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.TO, toAddress);
+        concat(toTerm);
+        return this;
+    }
+
+    /**
+     * Defines filter for CC field.
+     *
+     * @param ccAddress CC addreses.
+     * @return this
+     */
+    public EmailFilter cc(final String ccAddress) {
+        final SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.CC, ccAddress);
+        concat(toTerm);
+        return this;
+    }
+
+    /**
+     * Defines filter for BCC field.
+     *
+     * @param bccAddress BCC address.
+     * @return this
+     */
+    public EmailFilter bcc(final String bccAddress) {
+        final SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.BCC, bccAddress);
+        concat(toTerm);
+        return this;
+    }
+
+    /**
+     * Defines filter for many flags at once.
+     *
+     * @param flags The {@link Flags} to filter on.
+     * @param value The flag setting to check for.
+     * @return this
+     */
+    public EmailFilter flags(final Flags flags, final boolean value) {
+        final SearchTerm flagTerm = new FlagTerm(flags, value);
+        concat(flagTerm);
+        return this;
+    }
+
+    /**
+     * Defines filter for single flag.
+     *
+     * @param flag  The flag to filter on.
+     * @param value The flag setting to check for.
+     * @return this
+     */
+    public EmailFilter flag(final Flags.Flag flag, final boolean value) {
+        final Flags flags = new Flags();
+        flags.add(flag);
+        return flags(flags, value);
+    }
+
+    /**
+     * Defines filter for received date.
+     *
+     * @return this
+     */
+    public EmailFilter receivedDate(final Operator operator, final long milliseconds) {
+        final SearchTerm term = new ReceivedDateTerm(operator.value, new Date(milliseconds));
+        concat(term);
+        return this;
+    }
+
+    /**
+     * Defines filter for sent date.
+     *
+     * @param operator     {@link Operator} to use.
+     * @param milliseconds the milliseconds since January 1, 1970, 00:00:00 GMT.
+     * @return this
+     */
+    public EmailFilter sentDate(final Operator operator, final long milliseconds) {
+        final SearchTerm term = new SentDateTerm(operator.value, new Date(milliseconds));
+        concat(term);
+        return this;
+    }
+
+    /**
+     * Defines filter on a message body.
+     * All parts of the message that are of MIME type "text/*" are searched.
+     *
+     * @param pattern String pattern use in body.
+     * @return this
+     */
+    public EmailFilter text(final String pattern) {
+        final SearchTerm term = new BodyTerm(pattern);
+        concat(term);
+        return this;
+    }
+
+    /**
+     * Defines filter for {@link Header}.
+     *
+     * @param headerName The name of the {@link Header}.
+     * @param pattern    String pattern to use for headerName.
+     * @return this
+     */
+    public EmailFilter header(final String headerName, final String pattern) {
+        final SearchTerm term = new HeaderTerm(headerName, pattern);
+        concat(term);
+        return this;
+    }
+
+    /**
+     * Defines filter for message size.
+     *
+     * @param comparison {@link Operator}.
+     * @param size       size of message.
+     * @return this
+     */
+    public EmailFilter size(final Operator comparison, final int size) {
+        final SearchTerm term = new SizeTerm(comparison.value, size);
+        concat(term);
+        return this;
+    }
+
+    /**
+     * Changes concatenation mode to AND.
+     *
+     * @return this
+     */
+    public EmailFilter and() {
+        this.operatorAnd = true;
+        return this;
+    }
+
+    // ---------------------------------------------------------------- boolean
+
+    /**
+     * Changes concatenation mode to OR.
+     *
+     * @return this
+     */
+    public EmailFilter or() {
+        this.operatorAnd = false;
+        return this;
+    }
+
+    /**
+     * Marks next condition to be NOT.
+     *
+     * @return this
+     */
+    public EmailFilter not() {
+        this.nextIsNot = true;
+        return this;
+    }
+
+    /**
+     * Defines AND group of filters.
+     *
+     * @param emailFilters array of {@link EmailFilter}s to AND.
+     * @return this
+     */
+    public EmailFilter and(final EmailFilter... emailFilters) {
+        final SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];
+
+        for (int i = 0; i < emailFilters.length; i++) {
+            searchTerms[i] = emailFilters[i].searchTerm;
+        }
+
+        concat(new AndTerm(searchTerms));
+        return this;
+    }
+
+    /**
+     * Defines OR group of filters.
+     *
+     * @param emailFilters array of {@link EmailFilter}s to OR.
+     * @return this
+     */
+    public EmailFilter or(final EmailFilter... emailFilters) {
+        final SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];
+
+        for (int i = 0; i < emailFilters.length; i++) {
+            searchTerms[i] = emailFilters[i].searchTerm;
+        }
+
+        concat(new OrTerm(searchTerms));
+        return this;
+    }
+
+    /**
+     * Appends single filter as NOT.
+     *
+     * @param emailFilter {@link EmailFilter} to append.
+     * @return this
+     */
+    public EmailFilter not(final EmailFilter emailFilter) {
+        final SearchTerm searchTerm = new NotTerm(emailFilter.searchTerm);
+        concat(searchTerm);
+        return this;
+    }
+
+    /**
+     * Concatenates last search term with new one.
+     *
+     * @param searchTerm searchTerm {@link SearchTerm} concatenate.
+     * @see #and(SearchTerm)
+     * @see #or(SearchTerm)
+     */
+    protected void concat(SearchTerm searchTerm) {
+        if (nextIsNot) {
+            searchTerm = new NotTerm(searchTerm);
+            nextIsNot = false;
+        }
+        if (operatorAnd) {
+            and(searchTerm);
+        } else {
+            or(searchTerm);
+        }
+    }
+
+    // ---------------------------------------------------------------- concat
+
+    /**
+     * Sets {@link AndTerm} as searchTerm.
+     *
+     * @param searchTerm {@link SearchTerm} to set as AND.
+     */
+    protected void and(final SearchTerm searchTerm) {
+        if (this.searchTerm == null) {
+            this.searchTerm = searchTerm;
+            return;
+        }
+
+        this.searchTerm = new AndTerm(this.searchTerm, searchTerm);
+    }
+
+    /**
+     * Sets {@link OrTerm} searchTerm.
+     *
+     * @param searchTerm {@link SearchTerm} to set as OR.
+     */
+    protected void or(final SearchTerm searchTerm) {
+        if (this.searchTerm == null) {
+            this.searchTerm = searchTerm;
+            return;
+        }
+
+        this.searchTerm = new OrTerm(this.searchTerm, searchTerm);
+    }
+
+    /**
+     * Returns search term.
+     *
+     * @return {@link SearchTerm}.
+     */
+    public SearchTerm getSearchTerm() {
+        return searchTerm;
+    }
+
+    // ---------------------------------------------------------------- term
+
+    /**
+     * Comparison operator.
+     */
+    public enum Operator {
+        EQ(jakarta.mail.search.ComparisonTerm.EQ),
+        GE(jakarta.mail.search.ComparisonTerm.GE),
+        GT(jakarta.mail.search.ComparisonTerm.GT),
+        LE(jakarta.mail.search.ComparisonTerm.LE),
+        LT(jakarta.mail.search.ComparisonTerm.LT),
+        NE(jakarta.mail.search.ComparisonTerm.NE);
+
+        private final int value;
+
+        Operator(final int value) {
+            this.value = value;
+        }
+    }
 
 }

+ 58 - 58
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailMessage.java

@@ -30,70 +30,70 @@ package com.fjhx.utils.mail;
  */
 public class EmailMessage {
 
-	/**
-	 * The content as a {@link String}.
-	 */
-	private final String content;
+    /**
+     * The content as a {@link String}.
+     */
+    private final String content;
 
-	/**
-	 * The MIME type as a as a {@link String}.
-	 */
-	private final String mimeType;
+    /**
+     * The MIME type as a as a {@link String}.
+     */
+    private final String mimeType;
 
-	/**
-	 * The encoding as a {@link String}.
-	 */
-	private final String encoding;
+    /**
+     * The encoding as a {@link String}.
+     */
+    private final String encoding;
 
-	/**
-	 * Defines email content.
-	 *
-	 * @param content  The content as a {@link String}.
-	 * @param mimeType The MIME type as a as a {@link String}.
-	 * @param encoding The encoding as a {@link String}.
-	 */
-	public EmailMessage(final String content, final String mimeType, final String encoding) {
-		this.content = content;
-		this.mimeType = mimeType;
-		this.encoding = encoding;
-	}
+    /**
+     * Defines email content.
+     *
+     * @param content  The content as a {@link String}.
+     * @param mimeType The MIME type as a as a {@link String}.
+     * @param encoding The encoding as a {@link String}.
+     */
+    public EmailMessage(final String content, final String mimeType, final String encoding) {
+        this.content = content;
+        this.mimeType = mimeType;
+        this.encoding = encoding;
+    }
 
-	/**
-	 * Uses UTF-8 email content by default.
-	 *
-	 * @param content  The content as a {@link String}.
-	 * @param mimeType The MIME type as a as a {@link String}.
-	 */
-	public EmailMessage(final String content, final String mimeType) {
-		this(content, mimeType, "UTF-8");
-	}
+    /**
+     * Uses UTF-8 email content by default.
+     *
+     * @param content  The content as a {@link String}.
+     * @param mimeType The MIME type as a as a {@link String}.
+     */
+    public EmailMessage(final String content, final String mimeType) {
+        this(content, mimeType, "UTF-8");
+    }
 
-	// ---------------------------------------------------------------- getters
+    // ---------------------------------------------------------------- getters
 
-	/**
-	 * Returns message content.
-	 *
-	 * @return {@link String} containing the message content.
-	 */
-	public String getContent() {
-		return content;
-	}
+    /**
+     * Returns message content.
+     *
+     * @return {@link String} containing the message content.
+     */
+    public String getContent() {
+        return content;
+    }
 
-	/**
-	 * Returns message mime type.
-	 *
-	 * @return {@link String} containing the message mime type.
-	 */
-	public String getMimeType() {
-		return mimeType;
-	}
+    /**
+     * Returns message mime type.
+     *
+     * @return {@link String} containing the message mime type.
+     */
+    public String getMimeType() {
+        return mimeType;
+    }
 
-	/**
-	 * Returns message encoding.
-	 *
-	 * @return {@link String} containing the message encoding.
-	 */
-	public String getEncoding() {
-		return encoding;
-	}
+    /**
+     * Returns message encoding.
+     *
+     * @return {@link String} containing the message encoding.
+     */
+    public String getEncoding() {
+        return encoding;
+    }
 }

+ 176 - 182
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/EmailUtil.java

@@ -25,13 +25,7 @@
 
 package com.fjhx.utils.mail;
 
-import jakarta.mail.Authenticator;
-import jakarta.mail.Flags;
-import jakarta.mail.MessagingException;
-import jakarta.mail.NoSuchProviderException;
-import jakarta.mail.Part;
-import jakarta.mail.Session;
-import jakarta.mail.Store;
+import jakarta.mail.*;
 import jakarta.mail.internet.MimeBodyPart;
 import jakarta.mail.internet.MimeUtility;
 import jodd.util.CharUtil;
@@ -49,180 +43,180 @@ import java.util.function.Consumer;
  */
 public class EmailUtil {
 
-	protected static final String ATTR_CHARSET = "charset=";
-	static final String NO_NAME = "no-name";
-
-	/**
-	 * Extracts MIME type from content type.
-	 *
-	 * @param contentType MIME type.
-	 * @return MIME type for the given content type.
-	 */
-	//TODO: should this always return lowercase or always uppercase?
-	public static String extractMimeType(final String contentType) {
-		final int ndx = contentType.indexOf(';');
-		final String mime;
-		if (ndx != -1) {
-			mime = contentType.substring(0, ndx);
-		} else {
-			mime = contentType;
-		}
-		return mime;
-	}
-
-	/**
-	 * Extracts encoding from a given content type.
-	 *
-	 * @param contentType content type.
-	 * @return Encoding from the content type. May return {@code null} if encoding is not specified in content type.
-	 */
-	//TODO: should this always return lowercase or always uppercase?
-	public static String extractEncoding(final String contentType) {
-		int ndx = contentType.indexOf(';');
-		final String charset = ndx != -1 ? contentType.substring(ndx + 1) : StringPool.EMPTY;
-		String encoding = null;
-
-		ndx = charset.indexOf(ATTR_CHARSET);
-		if (ndx != -1) {
-			ndx += ATTR_CHARSET.length();
-			final int len = charset.length();
-
-			if (charset.charAt(ndx) == '"') {
-				ndx++;
-			}
-			final int start = ndx;
-
-			while (ndx < len) {
-				final char c = charset.charAt(ndx);
-				if ((c == '"') || (CharUtil.isWhitespace(c)) || (c == ';')) {
-					break;
-				}
-				ndx++;
-			}
-			encoding = charset.substring(start, ndx);
-		}
-		return encoding;
-	}
-
-	/**
-	 * Extracts encoding from a given content type.
-	 *
-	 * @param contentType     content type.
-	 * @param defaultEncoding Default encoding to be used if extract returns {@code null}.
-	 *                        If defaultEncoding is {@code null}, default encoding will be used.
-	 * @return Encoding from the content type.
-	 * @see #extractEncoding(String)
-	 */
-	public static String extractEncoding(final String contentType, String defaultEncoding) {
-		String encoding = extractEncoding(contentType);
-
-		if (encoding == null) {
-			if (defaultEncoding == null) {
-				defaultEncoding = "UTF-8";
-			}
-			encoding = defaultEncoding;
-		}
-		return encoding;
-	}
-
-	/**
-	 * Correctly resolves file name from the message part.
-	 * Thanx to: Flavio Pompermaier
-	 *
-	 * @param part {@link Part} to decode file name from.
-	 * @return String containing file name.
-	 */
-	public static String resolveFileName(final Part part) throws MessagingException {
-		if (!(part instanceof MimeBodyPart)) {
-			return part.getFileName();
-		}
-
-		final String contentType = part.getContentType();
-		String ret;
-
-		try {
-			ret = MimeUtility.decodeText(part.getFileName());
-		} catch (final Exception ex) {
-			// String[] contentId = part.getHeader("Content-ID");
-			// if (contentId != null && contentId.length > 0) {
-			final String contentId = ((MimeBodyPart) part).getContentID();
-			if (contentId != null) {
-				ret = contentId + contentTypeForFileName(contentType);
-			} else {
-				ret = defaultFileName(contentType);
-			}
-		}
-
-		return ret;
-	}
-
-	private static String contentTypeForFileName(final String contentType) {
-		return StringPool.DOT + contentType.substring(contentType.lastIndexOf("/") + 1, contentType.length());
-	}
-
-	private static String defaultFileName(final String contentType) {
-		return NO_NAME + contentTypeForFileName(contentType);
-	}
-
-	/**
-	 * @param protocol          Protocol such as {@link ImapServer#PROTOCOL_IMAP} or {@link Pop3Server#PROTOCOL_POP3}.
-	 * @param sessionProperties Session properties to use.
-	 * @param authenticator     Authenticator which contains necessary authentication for server.
-	 * @param debugConsumer
-	 * @return {@link ReceiveMailSession}.
-	 */
-	public static ReceiveMailSession createSession(final String protocol, final Properties sessionProperties, final Authenticator authenticator, final File attachmentStorage, final Consumer<String> debugConsumer) {
-		final Session session = Session.getInstance(sessionProperties, authenticator);
-		if (debugConsumer != null) {
-			session.setDebugOut(new PrintStream(new ByteArrayOutputStream() {
-				@Override
-				public void flush() throws IOException {
-					final String record;
-					synchronized (this) {
-						super.flush();
-						record = this.toString().trim();
-						super.reset();
-
-						if (record.length() == 0 || record.equals(System.lineSeparator())) {
-							// avoid empty records
-							return;
-						}
-
-						debugConsumer.accept(record);
-					}
-				}
-			}, true));
-		}
-		final Store store;
-		try {
-			store = session.getStore(protocol);
-		} catch (final NoSuchProviderException nspex) {
-			final String errMsg = String.format("Failed to create %s session", protocol);
-			throw new MailException(errMsg, nspex);
-		}
-		return new ReceiveMailSession(session, store, attachmentStorage);
-	}
-
-	/**
-	 * Check whether flags is a empty flags
-	 *
-	 * @param flags a flags of message to check
-	 * @return whether the flags is empty
-	 */
-	public static boolean isEmptyFlags(final Flags flags) {
-		if (flags == null) {
-			return true;
-		}
-		final Flags.Flag[] systemFlags = flags.getSystemFlags();
-		if (systemFlags != null && systemFlags.length > 0) {
-			return false;
-		}
-		final String[] userFlags = flags.getUserFlags();
-		if (userFlags != null && userFlags.length > 0) {
-			return false;
-		}
-
-		return true;
-	}
+    protected static final String ATTR_CHARSET = "charset=";
+    static final String NO_NAME = "no-name";
+
+    /**
+     * Extracts MIME type from content type.
+     *
+     * @param contentType MIME type.
+     * @return MIME type for the given content type.
+     */
+    // TODO: should this always return lowercase or always uppercase?
+    public static String extractMimeType(final String contentType) {
+        final int ndx = contentType.indexOf(';');
+        final String mime;
+        if (ndx != -1) {
+            mime = contentType.substring(0, ndx);
+        } else {
+            mime = contentType;
+        }
+        return mime;
+    }
+
+    /**
+     * Extracts encoding from a given content type.
+     *
+     * @param contentType content type.
+     * @return Encoding from the content type. May return {@code null} if encoding is not specified in content type.
+     */
+    // TODO: should this always return lowercase or always uppercase?
+    public static String extractEncoding(final String contentType) {
+        int ndx = contentType.indexOf(';');
+        final String charset = ndx != -1 ? contentType.substring(ndx + 1) : StringPool.EMPTY;
+        String encoding = null;
+
+        ndx = charset.indexOf(ATTR_CHARSET);
+        if (ndx != -1) {
+            ndx += ATTR_CHARSET.length();
+            final int len = charset.length();
+
+            if (charset.charAt(ndx) == '"') {
+                ndx++;
+            }
+            final int start = ndx;
+
+            while (ndx < len) {
+                final char c = charset.charAt(ndx);
+                if ((c == '"') || (CharUtil.isWhitespace(c)) || (c == ';')) {
+                    break;
+                }
+                ndx++;
+            }
+            encoding = charset.substring(start, ndx);
+        }
+        return encoding;
+    }
+
+    /**
+     * Extracts encoding from a given content type.
+     *
+     * @param contentType     content type.
+     * @param defaultEncoding Default encoding to be used if extract returns {@code null}.
+     *                        If defaultEncoding is {@code null}, default encoding will be used.
+     * @return Encoding from the content type.
+     * @see #extractEncoding(String)
+     */
+    public static String extractEncoding(final String contentType, String defaultEncoding) {
+        String encoding = extractEncoding(contentType);
+
+        if (encoding == null) {
+            if (defaultEncoding == null) {
+                defaultEncoding = "UTF-8";
+            }
+            encoding = defaultEncoding;
+        }
+        return encoding;
+    }
+
+    /**
+     * Correctly resolves file name from the message part.
+     * Thanx to: Flavio Pompermaier
+     *
+     * @param part {@link Part} to decode file name from.
+     * @return String containing file name.
+     */
+    public static String resolveFileName(final Part part) throws MessagingException {
+        if (!(part instanceof MimeBodyPart)) {
+            return part.getFileName();
+        }
+
+        final String contentType = part.getContentType();
+        String ret;
+
+        try {
+            ret = MimeUtility.decodeText(part.getFileName());
+        } catch (final Exception ex) {
+            // String[] contentId = part.getHeader("Content-ID");
+            // if (contentId != null && contentId.length > 0) {
+            final String contentId = ((MimeBodyPart) part).getContentID();
+            if (contentId != null) {
+                ret = contentId + contentTypeForFileName(contentType);
+            } else {
+                ret = defaultFileName(contentType);
+            }
+        }
+
+        return ret;
+    }
+
+    private static String contentTypeForFileName(final String contentType) {
+        return StringPool.DOT + contentType.substring(contentType.lastIndexOf("/") + 1, contentType.length());
+    }
+
+    private static String defaultFileName(final String contentType) {
+        return NO_NAME + contentTypeForFileName(contentType);
+    }
+
+    /**
+     * @param protocol          Protocol such as {@link ImapServer#PROTOCOL_IMAP} or {@link Pop3Server#PROTOCOL_POP3}.
+     * @param sessionProperties Session properties to use.
+     * @param authenticator     Authenticator which contains necessary authentication for server.
+     * @param debugConsumer
+     * @return {@link ReceiveMailSession}.
+     */
+    public static ReceiveMailSession createSession(final String protocol, final Properties sessionProperties, final Authenticator authenticator, final File attachmentStorage, final Consumer<String> debugConsumer) {
+        final Session session = Session.getInstance(sessionProperties, authenticator);
+        if (debugConsumer != null) {
+            session.setDebugOut(new PrintStream(new ByteArrayOutputStream() {
+                @Override
+                public void flush() throws IOException {
+                    final String record;
+                    synchronized (this) {
+                        super.flush();
+                        record = this.toString().trim();
+                        super.reset();
+
+                        if (record.length() == 0 || record.equals(System.lineSeparator())) {
+                            // avoid empty records
+                            return;
+                        }
+
+                        debugConsumer.accept(record);
+                    }
+                }
+            }, true));
+        }
+        final Store store;
+        try {
+            store = session.getStore(protocol);
+        } catch (final NoSuchProviderException nspex) {
+            final String errMsg = String.format("Failed to create %s session", protocol);
+            throw new MailException(errMsg, nspex);
+        }
+        return new ReceiveMailSession(session, store, attachmentStorage);
+    }
+
+    /**
+     * Check whether flags is a empty flags
+     *
+     * @param flags a flags of message to check
+     * @return whether the flags is empty
+     */
+    public static boolean isEmptyFlags(final Flags flags) {
+        if (flags == null) {
+            return true;
+        }
+        final Flags.Flag[] systemFlags = flags.getSystemFlags();
+        if (systemFlags != null && systemFlags.length > 0) {
+            return false;
+        }
+        final String[] userFlags = flags.getUserFlags();
+        if (userFlags != null && userFlags.length > 0) {
+            return false;
+        }
+
+        return true;
+    }
 
 }

+ 40 - 40
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ImapSslServer.java

@@ -38,53 +38,53 @@ import java.util.Properties;
  */
 public class ImapSslServer extends ImapServer {
 
-	/**
-	 * Default IMAP SSL port.
-	 */
-	protected static final int DEFAULT_SSL_PORT = 993;
+    /**
+     * Default IMAP SSL port.
+     */
+    protected static final int DEFAULT_SSL_PORT = 993;
 
-	public ImapSslServer(final Builder builder) {
-		super(builder, DEFAULT_SSL_PORT);
-	}
+    public ImapSslServer(final Builder builder) {
+        super(builder, DEFAULT_SSL_PORT);
+    }
 
-	@Override
-	protected Properties createSessionProperties() {
-		final Properties props = super.createSessionProperties();
+    @Override
+    protected Properties createSessionProperties() {
+        final Properties props = super.createSessionProperties();
 
-		props.setProperty(MAIL_IMAP_SOCKET_FACTORY_PORT, String.valueOf(port));
-		props.setProperty(MAIL_IMAP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
-		props.setProperty(MAIL_IMAP_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);
+        props.setProperty(MAIL_IMAP_SOCKET_FACTORY_PORT, String.valueOf(port));
+        props.setProperty(MAIL_IMAP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
+        props.setProperty(MAIL_IMAP_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);
 
-		return props;
-	}
+        return props;
+    }
 
-	/**
-	 * Returns email store.
-	 *
-	 * @param session {@link Session}
-	 * @return {@link IMAPSSLStore}
-	 */
-	@Override
-	protected IMAPSSLStore getStore(final Session session) {
-		final SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) authenticator;
+    /**
+     * Returns email store.
+     *
+     * @param session {@link Session}
+     * @return {@link IMAPSSLStore}
+     */
+    @Override
+    protected IMAPSSLStore getStore(final Session session) {
+        final SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) authenticator;
 
-		final URLName url;
+        final URLName url;
 
-		if (simpleAuthenticator == null) {
-			url = new URLName(
-					PROTOCOL_IMAP,
-					host, port,
-					StringPool.EMPTY, null, null);
-		} else {
-			final PasswordAuthentication pa = simpleAuthenticator.getPasswordAuthentication();
-			url = new URLName(
-					PROTOCOL_IMAP,
-					host, port,
-					StringPool.EMPTY,
-					pa.getUserName(), pa.getPassword());
-		}
+        if (simpleAuthenticator == null) {
+            url = new URLName(
+                    PROTOCOL_IMAP,
+                    host, port,
+                    StringPool.EMPTY, null, null);
+        } else {
+            final PasswordAuthentication pa = simpleAuthenticator.getPasswordAuthentication();
+            url = new URLName(
+                    PROTOCOL_IMAP,
+                    host, port,
+                    StringPool.EMPTY,
+                    pa.getUserName(), pa.getPassword());
+        }
 
-		return new IMAPSSLStore(session, url);
-	}
+        return new IMAPSSLStore(session, url);
+    }
 
 }

+ 25 - 25
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/MailException.java

@@ -32,31 +32,31 @@ import jodd.exception.UncheckedException;
  */
 public class MailException extends UncheckedException {
 
-	/**
-	 * Creates a {@link MailException}.
-	 *
-	 * @param message Error message.
-	 */
-	public MailException(final String message) {
-		super(message);
-	}
+    /**
+     * Creates a {@link MailException}.
+     *
+     * @param message Error message.
+     */
+    public MailException(final String message) {
+        super(message);
+    }
 
-	/**
-	 * Creates a {@link MailException}.
-	 *
-	 * @param message Error message.
-	 * @param t       {@link Throwable} which occurred.
-	 */
-	public MailException(final String message, final Throwable t) {
-		super(message, t);
-	}
+    /**
+     * Creates a {@link MailException}.
+     *
+     * @param message Error message.
+     * @param t       {@link Throwable} which occurred.
+     */
+    public MailException(final String message, final Throwable t) {
+        super(message, t);
+    }
 
-	/**
-	 * Creates a {@link MailException}.
-	 *
-	 * @param t {@link Throwable} which occurred.
-	 */
-	public MailException(final Throwable t) {
-		super(t);
-	}
+    /**
+     * Creates a {@link MailException}.
+     *
+     * @param t {@link Throwable} which occurred.
+     */
+    public MailException(final Throwable t) {
+        super(t);
+    }
 }

+ 335 - 337
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/MailServer.java

@@ -35,341 +35,339 @@ import java.util.function.Consumer;
 
 public abstract class MailServer<MailSessionImpl extends MailSession> {
 
-	// This is the list of all mail properties used by Jakarta Mail API and it's implementation, Angus.
-	// Jakarta: https://javaee.github.io/javamail/docs/api/
-	// Angus (smtp): https://github.com/eclipse-ee4j/angus-mail/blob/master/providers/smtp/src/main/java/org/eclipse/angus/mail/smtp/package-info.java
-	// Angus (imap): https://github.com/eclipse-ee4j/angus-mail/blob/master/providers/imap/src/main/java/org/eclipse/angus/mail/imap/package-info.java
-	// Angus (pop3): https://github.com/eclipse-ee4j/angus-mail/blob/master/providers/pop3/src/main/java/org/eclipse/angus/mail/pop3/package-info.java
-
-	public static final String MAIL_HOST = "mail.host";
-	public static final String MAIL_FROM = "mail.from";
-	public static final String MAIL_DEBUG = "mail.debug";
-	public static final String MAIL_TRANSPORT_PROTOCOL = "mail.transport.protocol";
-	public static final String MAIL_MIME_ADDRESS_STRICT = "mail.mime.address.strict";
-
-	public static final String MAIL_SMTP_HOST = "mail.smtp.host";
-	public static final String MAIL_SMTP_PORT = "mail.smtp.port";
-	public static final String MAIL_SMTP_USER = "mail.smtp.user";
-
-	public static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
-	public static final String MAIL_SMTP_FROM = "mail.smtp.from";
-	public static final String MAIL_SMTP_CONNECTIONTIMEOUT = "mail.smtp.connectiontimeout";
-	public static final String MAIL_SMTP_TIMEOUT = "mail.smtp.timeout";
-	public static final String MAIL_SMTP_WRITETIMEOUT = "mail.smtp.writetimeout";
-
-	public static final String MAIL_SMTP_STARTTLS_REQUIRED = "mail.smtp.starttls.required";
-	public static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable";
-	public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = "mail.smtp.socketFactory.port";
-	public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class";
-	public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
-
-
-	public static final String MAIL_IMAP_CONNECTIONTIMEOUT = "mail.imap.connectiontimeout";
-	public static final String MAIL_IMAP_TIMEOUT = "mail.imap.timeout";
-	public static final String MAIL_IMAP_PORT = "mail.imap.port";
-	public static final String MAIL_IMAP_HOST = "mail.imap.host";
-	public static final String MAIL_IMAP_USER = "mail.imap.user";
-	public static final String MAIL_IMAP_PARTIALFETCH = "mail.imap.partialfetch";
-	public static final String MAIL_IMAP_PEEK = "mail.imap.peek";
-
-	public static final String MAIL_IMAP_SOCKET_FACTORY_PORT = "mail.imap.socketFactory.port";
-	public static final String MAIL_IMAP_SOCKET_FACTORY_CLASS = "mail.imap.socketFactory.class";
-	public static final String MAIL_IMAP_SOCKET_FACTORY_FALLBACK = "mail.imap.socketFactory.fallback";
-
-	public static final String MAIL_POP3_PORT = "mail.pop3.port";
-	public static final String MAIL_POP3_HOST = "mail.pop3.host";
-	public static final String MAIL_POP3_USER = "mail.pop3.user";
-	public static final String MAIL_POP3_AUTH = "mail.pop3.auth";
-	public static final String MAIL_POP3_CONNECTIONTIMEOUT = "mail.pop3.connectiontimeout";
-	public static final String MAIL_POP3_TIMEOUT = "mail.pop3.timeout";
-
-	public static final String MAIL_POP3_SOCKET_FACTORY_PORT = "mail.pop3.socketFactory.port";
-	public static final String MAIL_POP3_SOCKET_FACTORY_CLASS = "mail.pop3.socketFactory.class";
-	public static final String MAIL_POP3_SOCKET_FACTORY_FALLBACK = "mail.pop3.socketFactory.fallback";
-
-	// NOT PART OF SPECIFICATION:
-
-	public static final String MAIL_EVENT_EXECUTOR = "mail.event.executor";
-	public static final String MAIL_EVENT_SCOPE = "mail.event.scope";
-	public static final String MAIL_DEBUG_AUTH = "mail.debug.auth";
-	public static final String MAIL_DEBUG_AUTH_USERNAME = "mail.debug.auth.username";
-	public static final String MAIL_DEBUG_AUTH_PASSWORD = "mail.debug.auth.password";
-
-	/**
-	 * The host.
-	 */
-	protected final String host;
-
-	/**
-	 * The port.
-	 */
-	protected final int port;
-
-	/**
-	 * The {@link Authenticator}.
-	 */
-	protected final Authenticator authenticator;
-
-	protected final File attachmentStorage;
-
-	protected final boolean debugMode;
-	protected final Consumer<String> debugConsumer;
-
-	/**
-	 * Whether strict address checking is turned on.
-	 */
-	protected final boolean strictAddress;
-
-	/**
-	 * Connection timeout.
-	 */
-	protected final int timeout;
-
-	protected final Properties customProperties;
-
-	/**
-	 * {@link MailServer} defined with its host, port and {@link Authenticator}.
-	 */
-	protected MailServer(final Builder builder, final int defaultPort) {
-		Objects.requireNonNull(builder.host, "Host cannot be null");
-
-		this.host = builder.host;
-		this.port = builder.port == -1 ? defaultPort : builder.port;
-		this.authenticator = builder.authenticator;
-		this.attachmentStorage = builder.attachmentStorage;
-		this.timeout = builder.timeout;
-		this.strictAddress = builder.strictAddress;
-		this.debugMode = builder.debug;
-		this.debugConsumer = builder.debugConsumer;
-		this.customProperties = builder.customProperties;
-	}
-
-	/**
-	 * Creates new mail session.
-	 *
-	 * @return {@link MailSession} or an implementing class such as {@link ReceiveMailSession}
-	 * or {@link SendMailSession}. The {@link Session} properties must be set <b>before</b>
-	 * the {@link Session} is created.
-	 */
-	public abstract MailSessionImpl createSession();
-
-	/**
-	 * Creates {@link MailSession} {@link Properties}.
-	 *
-	 * @return session {@link Properties}
-	 */
-	protected Properties createSessionProperties() {
-		final Properties props = new Properties();
-
-		props.putAll(customProperties);
-
-		if (debugMode) {
-			props.put(MAIL_DEBUG, "true");
-		}
-
-		if (!strictAddress) {
-			props.put(MAIL_MIME_ADDRESS_STRICT, "false");
-		}
-
-		return props;
-	}
-
-	/**
-	 * Returns new mail server builder.
-	 */
-	public static Builder create() {
-		return new Builder();
-	}
-
-	// ---------------------------------------------------------------- builder
-
-	/**
-	 * Used to create implementing instances of {@link MailServer}.
-	 *
-	 * @see ImapServer
-	 * @see ImapSslServer
-	 * @see Pop3Server
-	 * @see Pop3SslServer
-	 * @see SmtpServer
-	 * @see SmtpSslServer
-	 */
-	public static class Builder {
-		private String host = null;
-		private int port = -1;
-		private boolean ssl = false;
-		private Authenticator authenticator;
-		private File attachmentStorage;
-		private boolean debug;
-		private Consumer<String> debugConsumer;
-		private int timeout = 0;
-		private boolean strictAddress = true;
-		private final Properties customProperties = new Properties();
-
-		/**
-		 * Sets the host.
-		 *
-		 * @param host The host to set.
-		 * @return this
-		 */
-		public Builder host(final String host) {
-			this.host = host;
-			return this;
-		}
-
-		/**
-		 * Sets the port.
-		 *
-		 * @param port The port to set.
-		 * @return this
-		 *
-		 */
-		public Builder port(final int port) {
-			this.port = port;
-			return this;
-		}
-
-		/**
-		 * Sets the SSL implementation of the Mail server.
-		 *
-		 * @param ssl SSL flag
-		 * @return this
-		 */
-		public Builder ssl(final boolean ssl) {
-			this.ssl = ssl;
-			return this;
-		}
-
-		/**
-		 * Defines attachment storage, a folder where attachments will be saved.
-		 */
-		public Builder storeAttachmentsIn(final File attachmentStorage) {
-			this.attachmentStorage = attachmentStorage;
-			return this;
-		}
-
-		/**
-		 * Sets authenticator as {@link SimpleAuthenticator} using username and password.
-		 *
-		 * @param username The username to use.
-		 * @param password The password to use.
-		 * @return this
-		 */
-		public Builder auth(final String username, final String password) {
-			Objects.requireNonNull(username, "Username cannot be null");
-			Objects.requireNonNull(password, "Password cannot be null");
-
-			return auth(new SimpleAuthenticator(username, password));
-		}
-
-		/**
-		 * Sets the authenticator.
-		 *
-		 * @param authenticator {@link Authenticator} to set.
-		 * @return this
-		 */
-		public Builder auth(final Authenticator authenticator) {
-			this.authenticator = authenticator;
-			return this;
-		}
-
-		/**
-		 * Enable or disable debug mode.
-		 *
-		 * @param debug {@code true} to turn on debugging. By default, this is {@code false}.
-		 * @return this
-		 */
-		public Builder debugMode(final boolean debug) {
-			this.debug = debug;
-			return this;
-		}
-
-		/**
-		 * Set debug consumer
-		 *
-		 * @param consumer a String consumer to be called for debug logging. By default, this is null.
-		 * @return this
-		 */
-		public Builder debugConsumer(final Consumer<String> consumer) {
-			this.debugConsumer = consumer;
-			return this;
-		}
-		/**
-		 * Defines timeout value in milliseconds for all mail-related operations.
-		 *
-		 * @param timeout timeout value in milliseconds.
-		 * @return this
-		 */
-		public Builder timeout(final int timeout) {
-			this.timeout = timeout;
-			return this;
-		}
-
-		/**
-		 * Disables the strict address.
-		 *
-		 * @param strictAddress {@code true} if strict address checking should be turned on. By default, this is {@code true}.
-		 * @return this
-		 */
-		public Builder strictAddress(final boolean strictAddress) {
-			this.strictAddress = strictAddress;
-			return this;
-		}
-
-		/**
-		 * Specifies a custom property.
-		 */
-		public Builder property(final String name, final String value) {
-			this.customProperties.put(name, value);
-			return this;
-		}
-
-		/**
-		 * Specifies a non-string custom property; such as `mail.event.executor`.
-		 */
-		public Builder property(final String name, final Object value) {
-			this.customProperties.put(name, value);
-			return this;
-		}
-
-		// ---------------------------------------------------------------- build
-
-		/**
-		 * Create a {@link ImapServer} from current data.
-		 *
-		 * @return {@link ImapServer} from current data.
-		 */
-		public ImapServer buildImapMailServer() {
-			if (ssl) {
-				return new ImapSslServer(this);
-			}
-			return new ImapServer(this);
-		}
-
-		/**
-		 * Create a {@link Pop3Server} from current data.
-		 *
-		 * @return {@link Pop3Server} from current data.
-		 *
-		 */
-		public Pop3Server buildPop3MailServer() {
-			if (ssl) {
-				return new Pop3SslServer(this);
-			}
-			return new Pop3Server(this);
-		}
-
-		/**
-		 * Create a {@link SmtpServer} from current data.
-		 *
-		 * @return {@link SmtpServer} from current data.
-		 *
-		 */
-		public SmtpServer buildSmtpMailServer() {
-			if (ssl) {
-				return new SmtpSslServer(this);
-			}
-			return new SmtpServer(this);
-		}
-
-	}
+    // This is the list of all mail properties used by Jakarta Mail API and it's implementation, Angus.
+    // Jakarta: https://javaee.github.io/javamail/docs/api/
+    // Angus (smtp): https://github.com/eclipse-ee4j/angus-mail/blob/master/providers/smtp/src/main/java/org/eclipse/angus/mail/smtp/package-info.java
+    // Angus (imap): https://github.com/eclipse-ee4j/angus-mail/blob/master/providers/imap/src/main/java/org/eclipse/angus/mail/imap/package-info.java
+    // Angus (pop3): https://github.com/eclipse-ee4j/angus-mail/blob/master/providers/pop3/src/main/java/org/eclipse/angus/mail/pop3/package-info.java
+
+    public static final String MAIL_HOST = "mail.host";
+    public static final String MAIL_FROM = "mail.from";
+    public static final String MAIL_DEBUG = "mail.debug";
+    public static final String MAIL_TRANSPORT_PROTOCOL = "mail.transport.protocol";
+    public static final String MAIL_MIME_ADDRESS_STRICT = "mail.mime.address.strict";
+
+    public static final String MAIL_SMTP_HOST = "mail.smtp.host";
+    public static final String MAIL_SMTP_PORT = "mail.smtp.port";
+    public static final String MAIL_SMTP_USER = "mail.smtp.user";
+
+    public static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
+    public static final String MAIL_SMTP_FROM = "mail.smtp.from";
+    public static final String MAIL_SMTP_CONNECTIONTIMEOUT = "mail.smtp.connectiontimeout";
+    public static final String MAIL_SMTP_TIMEOUT = "mail.smtp.timeout";
+    public static final String MAIL_SMTP_WRITETIMEOUT = "mail.smtp.writetimeout";
+
+    public static final String MAIL_SMTP_STARTTLS_REQUIRED = "mail.smtp.starttls.required";
+    public static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable";
+    public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = "mail.smtp.socketFactory.port";
+    public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class";
+    public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
+
+
+    public static final String MAIL_IMAP_CONNECTIONTIMEOUT = "mail.imap.connectiontimeout";
+    public static final String MAIL_IMAP_TIMEOUT = "mail.imap.timeout";
+    public static final String MAIL_IMAP_PORT = "mail.imap.port";
+    public static final String MAIL_IMAP_HOST = "mail.imap.host";
+    public static final String MAIL_IMAP_USER = "mail.imap.user";
+    public static final String MAIL_IMAP_PARTIALFETCH = "mail.imap.partialfetch";
+    public static final String MAIL_IMAP_PEEK = "mail.imap.peek";
+
+    public static final String MAIL_IMAP_SOCKET_FACTORY_PORT = "mail.imap.socketFactory.port";
+    public static final String MAIL_IMAP_SOCKET_FACTORY_CLASS = "mail.imap.socketFactory.class";
+    public static final String MAIL_IMAP_SOCKET_FACTORY_FALLBACK = "mail.imap.socketFactory.fallback";
+
+    public static final String MAIL_POP3_PORT = "mail.pop3.port";
+    public static final String MAIL_POP3_HOST = "mail.pop3.host";
+    public static final String MAIL_POP3_USER = "mail.pop3.user";
+    public static final String MAIL_POP3_AUTH = "mail.pop3.auth";
+    public static final String MAIL_POP3_CONNECTIONTIMEOUT = "mail.pop3.connectiontimeout";
+    public static final String MAIL_POP3_TIMEOUT = "mail.pop3.timeout";
+
+    public static final String MAIL_POP3_SOCKET_FACTORY_PORT = "mail.pop3.socketFactory.port";
+    public static final String MAIL_POP3_SOCKET_FACTORY_CLASS = "mail.pop3.socketFactory.class";
+    public static final String MAIL_POP3_SOCKET_FACTORY_FALLBACK = "mail.pop3.socketFactory.fallback";
+
+    // NOT PART OF SPECIFICATION:
+
+    public static final String MAIL_EVENT_EXECUTOR = "mail.event.executor";
+    public static final String MAIL_EVENT_SCOPE = "mail.event.scope";
+    public static final String MAIL_DEBUG_AUTH = "mail.debug.auth";
+    public static final String MAIL_DEBUG_AUTH_USERNAME = "mail.debug.auth.username";
+    public static final String MAIL_DEBUG_AUTH_PASSWORD = "mail.debug.auth.password";
+
+    /**
+     * The host.
+     */
+    protected final String host;
+
+    /**
+     * The port.
+     */
+    protected final int port;
+
+    /**
+     * The {@link Authenticator}.
+     */
+    protected final Authenticator authenticator;
+
+    protected final File attachmentStorage;
+
+    protected final boolean debugMode;
+    protected final Consumer<String> debugConsumer;
+
+    /**
+     * Whether strict address checking is turned on.
+     */
+    protected final boolean strictAddress;
+
+    /**
+     * Connection timeout.
+     */
+    protected final int timeout;
+
+    protected final Properties customProperties;
+
+    /**
+     * {@link MailServer} defined with its host, port and {@link Authenticator}.
+     */
+    protected MailServer(final Builder builder, final int defaultPort) {
+        Objects.requireNonNull(builder.host, "Host cannot be null");
+
+        this.host = builder.host;
+        this.port = builder.port == -1 ? defaultPort : builder.port;
+        this.authenticator = builder.authenticator;
+        this.attachmentStorage = builder.attachmentStorage;
+        this.timeout = builder.timeout;
+        this.strictAddress = builder.strictAddress;
+        this.debugMode = builder.debug;
+        this.debugConsumer = builder.debugConsumer;
+        this.customProperties = builder.customProperties;
+    }
+
+    /**
+     * Returns new mail server builder.
+     */
+    public static Builder create() {
+        return new Builder();
+    }
+
+    /**
+     * Creates new mail session.
+     *
+     * @return {@link MailSession} or an implementing class such as {@link ReceiveMailSession}
+     * or {@link SendMailSession}. The {@link Session} properties must be set <b>before</b>
+     * the {@link Session} is created.
+     */
+    public abstract MailSessionImpl createSession();
+
+    /**
+     * Creates {@link MailSession} {@link Properties}.
+     *
+     * @return session {@link Properties}
+     */
+    protected Properties createSessionProperties() {
+        final Properties props = new Properties();
+
+        props.putAll(customProperties);
+
+        if (debugMode) {
+            props.put(MAIL_DEBUG, "true");
+        }
+
+        if (!strictAddress) {
+            props.put(MAIL_MIME_ADDRESS_STRICT, "false");
+        }
+
+        return props;
+    }
+
+    // ---------------------------------------------------------------- builder
+
+    /**
+     * Used to create implementing instances of {@link MailServer}.
+     *
+     * @see ImapServer
+     * @see ImapSslServer
+     * @see Pop3Server
+     * @see Pop3SslServer
+     * @see SmtpServer
+     * @see SmtpSslServer
+     */
+    public static class Builder {
+        private final Properties customProperties = new Properties();
+        private String host = null;
+        private int port = -1;
+        private boolean ssl = false;
+        private Authenticator authenticator;
+        private File attachmentStorage;
+        private boolean debug;
+        private Consumer<String> debugConsumer;
+        private int timeout = 0;
+        private boolean strictAddress = true;
+
+        /**
+         * Sets the host.
+         *
+         * @param host The host to set.
+         * @return this
+         */
+        public Builder host(final String host) {
+            this.host = host;
+            return this;
+        }
+
+        /**
+         * Sets the port.
+         *
+         * @param port The port to set.
+         * @return this
+         */
+        public Builder port(final int port) {
+            this.port = port;
+            return this;
+        }
+
+        /**
+         * Sets the SSL implementation of the Mail server.
+         *
+         * @param ssl SSL flag
+         * @return this
+         */
+        public Builder ssl(final boolean ssl) {
+            this.ssl = ssl;
+            return this;
+        }
+
+        /**
+         * Defines attachment storage, a folder where attachments will be saved.
+         */
+        public Builder storeAttachmentsIn(final File attachmentStorage) {
+            this.attachmentStorage = attachmentStorage;
+            return this;
+        }
+
+        /**
+         * Sets authenticator as {@link SimpleAuthenticator} using username and password.
+         *
+         * @param username The username to use.
+         * @param password The password to use.
+         * @return this
+         */
+        public Builder auth(final String username, final String password) {
+            Objects.requireNonNull(username, "Username cannot be null");
+            Objects.requireNonNull(password, "Password cannot be null");
+
+            return auth(new SimpleAuthenticator(username, password));
+        }
+
+        /**
+         * Sets the authenticator.
+         *
+         * @param authenticator {@link Authenticator} to set.
+         * @return this
+         */
+        public Builder auth(final Authenticator authenticator) {
+            this.authenticator = authenticator;
+            return this;
+        }
+
+        /**
+         * Enable or disable debug mode.
+         *
+         * @param debug {@code true} to turn on debugging. By default, this is {@code false}.
+         * @return this
+         */
+        public Builder debugMode(final boolean debug) {
+            this.debug = debug;
+            return this;
+        }
+
+        /**
+         * Set debug consumer
+         *
+         * @param consumer a String consumer to be called for debug logging. By default, this is null.
+         * @return this
+         */
+        public Builder debugConsumer(final Consumer<String> consumer) {
+            this.debugConsumer = consumer;
+            return this;
+        }
+
+        /**
+         * Defines timeout value in milliseconds for all mail-related operations.
+         *
+         * @param timeout timeout value in milliseconds.
+         * @return this
+         */
+        public Builder timeout(final int timeout) {
+            this.timeout = timeout;
+            return this;
+        }
+
+        /**
+         * Disables the strict address.
+         *
+         * @param strictAddress {@code true} if strict address checking should be turned on. By default, this is {@code true}.
+         * @return this
+         */
+        public Builder strictAddress(final boolean strictAddress) {
+            this.strictAddress = strictAddress;
+            return this;
+        }
+
+        /**
+         * Specifies a custom property.
+         */
+        public Builder property(final String name, final String value) {
+            this.customProperties.put(name, value);
+            return this;
+        }
+
+        /**
+         * Specifies a non-string custom property; such as `mail.event.executor`.
+         */
+        public Builder property(final String name, final Object value) {
+            this.customProperties.put(name, value);
+            return this;
+        }
+
+        // ---------------------------------------------------------------- build
+
+        /**
+         * Create a {@link ImapServer} from current data.
+         *
+         * @return {@link ImapServer} from current data.
+         */
+        public ImapServer buildImapMailServer() {
+            if (ssl) {
+                return new ImapSslServer(this);
+            }
+            return new ImapServer(this);
+        }
+
+        /**
+         * Create a {@link Pop3Server} from current data.
+         *
+         * @return {@link Pop3Server} from current data.
+         */
+        public Pop3Server buildPop3MailServer() {
+            if (ssl) {
+                return new Pop3SslServer(this);
+            }
+            return new Pop3Server(this);
+        }
+
+        /**
+         * Create a {@link SmtpServer} from current data.
+         *
+         * @return {@link SmtpServer} from current data.
+         */
+        public SmtpServer buildSmtpMailServer() {
+            if (ssl) {
+                return new SmtpSslServer(this);
+            }
+            return new SmtpServer(this);
+        }
+
+    }
 }

+ 79 - 83
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/MailSession.java

@@ -26,11 +26,7 @@
 package com.fjhx.utils.mail;
 
 
-import jakarta.mail.MessagingException;
-import jakarta.mail.Service;
-import jakarta.mail.Session;
-import jakarta.mail.Store;
-import jakarta.mail.Transport;
+import jakarta.mail.*;
 import jakarta.mail.internet.MimeUtility;
 
 /**
@@ -38,90 +34,90 @@ import jakarta.mail.internet.MimeUtility;
  */
 abstract class MailSession<T extends Service> implements AutoCloseable {
 
-	public static class Defaults {
-		/**
-		 * If set to {@code true}, the setFileName method uses the
-		 * {@link MimeUtility#encodeText(String)} to encode any non-ASCII characters in the filename.
-		 * Note that this encoding violates the MIME specification, but is useful for interoperating
-		 * with some mail clients that use this convention. The default is {@code true}.
-		 */
-		public static boolean mailMimeEncodefilename = true;
-		/**
-		 * If set to {@code true}, the setFileName method uses the
-		 * {@link MimeUtility#encodeText(String)} to encode any non-ASCII characters in the filename.
-		 * Note that this encoding violates the MIME specification, but is useful for interoperating
-		 * with some mail clients that use this convention. The default is {@code true}.
-		 */
-		public static boolean mailMimeDecodefilename = true;
-	}
+    protected final Service service;
+    private final Session session;
 
-	/**
-	 * Setups the system email properties.
-	 */
-	protected static void setupSystemMailProperties() {
-		System.setProperty("mail.mime.encodefilename", Boolean.valueOf(Defaults.mailMimeEncodefilename).toString());
-		System.setProperty("mail.mime.decodefilename", Boolean.valueOf(Defaults.mailMimeDecodefilename).toString());
-	}
+    /**
+     * Creates new mail session.
+     *
+     * @param session {@link Session}.
+     * @param service {@link Service} such as {@link Store} or {@link Transport}.
+     */
+    protected MailSession(final Session session, final Service service) {
+        this.session = session;
+        this.service = service;
+    }
 
-	private final Session session;
-	protected final Service service;
+    /**
+     * Setups the system email properties.
+     */
+    protected static void setupSystemMailProperties() {
+        System.setProperty("mail.mime.encodefilename", Boolean.valueOf(Defaults.mailMimeEncodefilename).toString());
+        System.setProperty("mail.mime.decodefilename", Boolean.valueOf(Defaults.mailMimeDecodefilename).toString());
+    }
 
-	/**
-	 * Creates new mail session.
-	 *
-	 * @param session {@link Session}.
-	 * @param service {@link Service} such as {@link Store} or {@link Transport}.
-	 */
-	protected MailSession(final Session session, final Service service) {
-		this.session = session;
-		this.service = service;
-	}
+    /**
+     * Opens session.
+     */
+    public void open() {
+        try {
+            service.connect();
+        } catch (final MessagingException msex) {
+            throw new MailException("Open session error", msex);
+        }
+    }
 
-	/**
-	 * Opens session.
-	 */
-	public void open() {
-		try {
-			service.connect();
-		} catch (final MessagingException msex) {
-			throw new MailException("Open session error", msex);
-		}
-	}
+    /**
+     * Closes session.
+     */
+    @Override
+    public void close() {
+        try {
+            service.close();
+        } catch (final MessagingException mex) {
+            throw new MailException("Failed to close session", mex);
+        }
+    }
 
-	/**
-	 * Closes session.
-	 */
-	@Override
-	public void close() {
-		try {
-			service.close();
-		} catch (final MessagingException mex) {
-			throw new MailException("Failed to close session", mex);
-		}
-	}
+    /**
+     * Returns {@code true} if mail session is still connected.
+     *
+     * @return {@code true} if mail session is still connected.
+     */
+    public boolean isConnected() {
+        return service.isConnected();
+    }
 
-	/**
-	 * Returns {@code true} if mail session is still connected.
-	 *
-	 * @return {@code true} if mail session is still connected.
-	 */
-	public boolean isConnected() {
-		return service.isConnected();
-	}
+    /**
+     * Returns the {@link Session}.
+     *
+     * @return the {@link Session}.
+     */
+    public Session getSession() {
+        return session;
+    }
 
-	/**
-	 * Returns the {@link Session}.
-	 *
-	 * @return the {@link Session}.
-	 */
-	public Session getSession() {
-		return session;
-	}
+    /**
+     * Returns the {@link Service}.
+     *
+     * @return the {@link Service}.
+     */
+    public abstract T getService();
 
-	/**
-	 * Returns the {@link Service}.
-	 *
-	 * @return the {@link Service}.
-	 */
-	public abstract T getService();
+    public static class Defaults {
+        /**
+         * If set to {@code true}, the setFileName method uses the
+         * {@link MimeUtility#encodeText(String)} to encode any non-ASCII characters in the filename.
+         * Note that this encoding violates the MIME specification, but is useful for interoperating
+         * with some mail clients that use this convention. The default is {@code true}.
+         */
+        public static boolean mailMimeEncodefilename = true;
+        /**
+         * If set to {@code true}, the setFileName method uses the
+         * {@link MimeUtility#encodeText(String)} to encode any non-ASCII characters in the filename.
+         * Note that this encoding violates the MIME specification, but is useful for interoperating
+         * with some mail clients that use this convention. The default is {@code true}.
+         */
+        public static boolean mailMimeDecodefilename = true;
+    }
 }

+ 59 - 59
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/Pop3Server.java

@@ -41,64 +41,64 @@ import static jodd.util.StringPool.TRUE;
  */
 public class Pop3Server extends MailServer<ReceiveMailSession> {
 
-	protected static final String PROTOCOL_POP3 = "pop3";
-
-	/**
-	 * Default POP3 port.
-	 */
-	protected static final int DEFAULT_POP3_PORT = 110;
-
-	public Pop3Server(final Builder builder) {
-		super(builder, DEFAULT_POP3_PORT);
-	}
-
-	protected Pop3Server(final Builder builder, final int defaultPort) {
-		super(builder, defaultPort);
-	}
-
-	@Override
-	protected Properties createSessionProperties() {
-		final Properties props = super.createSessionProperties();
-
-		props.setProperty(MAIL_POP3_HOST, host);
-		props.setProperty(MAIL_POP3_PORT, String.valueOf(port));
-
-		if (authenticator != null) {
-			props.setProperty(MAIL_POP3_AUTH, TRUE);
-		}
-
-		if (timeout > 0) {
-			final String timeoutValue = String.valueOf(timeout);
-			props.put(MAIL_POP3_CONNECTIONTIMEOUT, timeoutValue);
-			props.put(MAIL_POP3_TIMEOUT, timeoutValue);
-		}
-
-		return props;
-	}
-
-	/**
-	 * Returns email store.
-	 *
-	 * @return POP3Store
-	 * @throws NoSuchProviderException If a provider for the given protocol is not found.
-	 */
-	protected Store getStore(final Session session) throws NoSuchProviderException {
-		return session.getStore(PROTOCOL_POP3);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 *
-	 * @return {@link ReceiveMailSession}
-	 * @see EmailUtil#createSession(String, Properties, Authenticator, File, Consumer)
-	 */
-	@Override
-	public ReceiveMailSession createSession() {
-		return EmailUtil.createSession(
-				PROTOCOL_POP3,
-				createSessionProperties(),
-				authenticator,
-				attachmentStorage, debugConsumer);
-	}
+    protected static final String PROTOCOL_POP3 = "pop3";
+
+    /**
+     * Default POP3 port.
+     */
+    protected static final int DEFAULT_POP3_PORT = 110;
+
+    public Pop3Server(final Builder builder) {
+        super(builder, DEFAULT_POP3_PORT);
+    }
+
+    protected Pop3Server(final Builder builder, final int defaultPort) {
+        super(builder, defaultPort);
+    }
+
+    @Override
+    protected Properties createSessionProperties() {
+        final Properties props = super.createSessionProperties();
+
+        props.setProperty(MAIL_POP3_HOST, host);
+        props.setProperty(MAIL_POP3_PORT, String.valueOf(port));
+
+        if (authenticator != null) {
+            props.setProperty(MAIL_POP3_AUTH, TRUE);
+        }
+
+        if (timeout > 0) {
+            final String timeoutValue = String.valueOf(timeout);
+            props.put(MAIL_POP3_CONNECTIONTIMEOUT, timeoutValue);
+            props.put(MAIL_POP3_TIMEOUT, timeoutValue);
+        }
+
+        return props;
+    }
+
+    /**
+     * Returns email store.
+     *
+     * @return POP3Store
+     * @throws NoSuchProviderException If a provider for the given protocol is not found.
+     */
+    protected Store getStore(final Session session) throws NoSuchProviderException {
+        return session.getStore(PROTOCOL_POP3);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@link ReceiveMailSession}
+     * @see EmailUtil#createSession(String, Properties, Authenticator, File, Consumer)
+     */
+    @Override
+    public ReceiveMailSession createSession() {
+        return EmailUtil.createSession(
+                PROTOCOL_POP3,
+                createSessionProperties(),
+                authenticator,
+                attachmentStorage, debugConsumer);
+    }
 
 }

+ 38 - 38
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/Pop3SslServer.java

@@ -38,50 +38,50 @@ import java.util.Properties;
  */
 public class Pop3SslServer extends Pop3Server {
 
-	protected static final int DEFAULT_SSL_PORT = 995;
+    protected static final int DEFAULT_SSL_PORT = 995;
 
-	public Pop3SslServer(final Builder builder) {
-		super(builder, DEFAULT_SSL_PORT);
-	}
+    public Pop3SslServer(final Builder builder) {
+        super(builder, DEFAULT_SSL_PORT);
+    }
 
-	@Override
-	protected Properties createSessionProperties() {
-		final Properties props = super.createSessionProperties();
+    @Override
+    protected Properties createSessionProperties() {
+        final Properties props = super.createSessionProperties();
 
-		props.setProperty(MAIL_POP3_SOCKET_FACTORY_PORT, String.valueOf(port));
-		props.setProperty(MAIL_POP3_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
-		props.setProperty(MAIL_POP3_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);
+        props.setProperty(MAIL_POP3_SOCKET_FACTORY_PORT, String.valueOf(port));
+        props.setProperty(MAIL_POP3_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
+        props.setProperty(MAIL_POP3_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);
 
-		return props;
-	}
+        return props;
+    }
 
-	/**
-	 * Returns email store.
-	 *
-	 * @param session {@link Session}
-	 * @return POP3SSLStore
-	 */
-	@Override
-	protected POP3SSLStore getStore(final Session session) {
-		final SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) authenticator;
-		final URLName url;
+    /**
+     * Returns email store.
+     *
+     * @param session {@link Session}
+     * @return POP3SSLStore
+     */
+    @Override
+    protected POP3SSLStore getStore(final Session session) {
+        final SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) authenticator;
+        final URLName url;
 
-		if (simpleAuthenticator == null) {
-			url = new URLName(
-					PROTOCOL_POP3,
-					host, port,
-					StringPool.EMPTY,
-					null, null);
-		} else {
-			final PasswordAuthentication pa = simpleAuthenticator.getPasswordAuthentication();
-			url = new URLName(
-					PROTOCOL_POP3,
-					host, port,
-					StringPool.EMPTY,
-					pa.getUserName(), pa.getPassword());
-		}
+        if (simpleAuthenticator == null) {
+            url = new URLName(
+                    PROTOCOL_POP3,
+                    host, port,
+                    StringPool.EMPTY,
+                    null, null);
+        } else {
+            final PasswordAuthentication pa = simpleAuthenticator.getPasswordAuthentication();
+            url = new URLName(
+                    PROTOCOL_POP3,
+                    host, port,
+                    StringPool.EMPTY,
+                    pa.getUserName(), pa.getPassword());
+        }
 
-		return new POP3SSLStore(session, url);
-	}
+        return new POP3SSLStore(session, url);
+    }
 
 }

+ 645 - 650
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/RFC2822AddressParser.java

@@ -42,694 +42,689 @@ import java.util.regex.Pattern;
  * characters.
  */
 public class RFC2822AddressParser {
-	private boolean ALLOW_DOMAIN_LITERALS = false;
-	private boolean ALLOW_QUOTED_IDENTIFIERS = true;
-	private boolean ALLOW_DOT_IN_ATEXT = false;
-	private boolean EXTRACT_CFWS_PERSONAL_NAMES = true;
-	private boolean ALLOW_SQUARE_BRACKETS_IN_ATEXT = false;
-	private boolean ALLOW_PARENS_IN_LOCALPART = true;
-
-	/**
-	 * Strict parser.
-	 */
-	public static final RFC2822AddressParser STRICT =
-		new RFC2822AddressParser()
-			.allowDomainLiterals(true)
-			.allowQuotedIdentifiers(true)
-			.allowDotInAtext(false)
-			.extractCfwsPersonalName(true)
-			.allowSquareBracketsInAtext(false)
-			.allowParentheseInLocalpart(true);
-
-	/**
-	 * Loose parser.
-	 */
-	public static final RFC2822AddressParser LOOSE = new RFC2822AddressParser();
-
-	/**
-	 * Changes the behavior of the domain parsing. If {@code true}, the parser will
-	 * allow 2822 domains, which include single-level domains (e.g. bob@localhost) as well
-	 * as domain literals, e.g.:
-	 * <p>
-	 * <ul>
-	 * <li><code>someone@[192.168.1.100]</code> or</li>
-	 * <li><code>john.doe@[23:33:A2:22:16:1F]</code> or</li>
-	 * <li><code>me@[my computer]</code></li>
-	 * </ul>
-	 * <p>
-	 * The RFC says these are valid email addresses, but many don't like
-	 * allowing them. If you don't want to allow them, and only want to allow valid domain names
-	 * (<a href="http://www.ietf.org/rfc/rfc1035.txt">RFC 1035</a>, x.y.z.com, etc),
-	 * and specifically only those with at least two levels ("example.com"), then
-	 * set this flag to {@code false}.
-	 */
-	public RFC2822AddressParser allowDomainLiterals(final boolean allow) {
-		ALLOW_DOMAIN_LITERALS = allow;
-		resetPatterns();
-		return this;
-	}
-
-	/**
-	 * Defines if quoted identifiers are allowed.
-	 * Using quotes and angle brackets around the raw address may be allowed, e.g.:
-	 * <p>
-	 * <ul>
-	 * <li><code>"John Smith" &lt;john.smith@somewhere.com&gt;</code></li>
-	 * </ul>
-	 * <p>
-	 * The RFC says this is a valid mailbox. If you don't want to
-	 * allow this, because for example, you only want users to enter in
-	 * a raw address (<code>john.smith@somewhere.com</code> - no quotes or angle
-	 * brackets), then set the flag {@code false}.
-	 */
-	public RFC2822AddressParser allowQuotedIdentifiers(final boolean allow) {
-		ALLOW_QUOTED_IDENTIFIERS = allow;
-		resetPatterns();
-		return this;
-	}
-
-	/**
-	 * Allows &quot;.&quot; to appear in atext (note: only atext which appears
-	 * in the 2822 &quot;name-addr&quot; part of the address, not the other instances).
-	 * <p>
-	 * The addresses:
-	 * <ul>
-	 * <li><code>Kayaks.org &lt;kayaks@kayaks.org&gt;</code></li>
-	 * <li><code>Bob K. Smith&lt;bobksmith@bob.net&gt;</code></li>
-	 * </ul>
-	 * ...are not valid. They should be:
-	 * <ul>
-	 * <li><code>&quot;Kayaks.org&quot; &lt;kayaks@kayaks.org&gt;</code></li>
-	 * <li><code>&quot;Bob K. Smith&quot; &lt;bobksmith@bob.net&gt;</code></li>
-	 * </ul>
-	 * If this boolean is set to false, the parser will act per 2822 and will require
-	 * the quotes; if set to true, it will allow the use of &quot;.&quot; without quotes.
-	 */
-	public RFC2822AddressParser allowDotInAtext(final boolean allow) {
-		ALLOW_DOT_IN_ATEXT = allow;
-		resetPatterns();
-		return this;
-	}
-
-	/**
-	 * Controls the behavior of getInternetAddress. If true, allows the real world practice of:
-	 * <ul>
-	 * <li>&lt;bob@example.com&gt; (Bob Smith)</li>
-	 * </ul>
-	 * <p>
-	 * In this case, &quot;Bob Smith&quot; is not technically the personal name, just a
-	 * comment. If this is set to true, the methods will convert this into:
-	 * <ul>
-	 * <li>Bob Smith &lt;bob@example.com&gt;</li>
-	 * </ul>
-	 * <p>
-	 * This also happens somewhat more often and appropriately with
-	 * <code>mailer-daemon@blah.com (Mail Delivery System)</code>.
-	 * <p>
-	 * <p>
-	 * If a personal name appears to the left and CFWS appears to the right of an address,
-	 * the methods will favor the personal name to the left. If the methods need to use the
-	 * CFWS following the address, they will take the first comment token they find.
-	 */
-	public RFC2822AddressParser extractCfwsPersonalName(final boolean extract) {
-		EXTRACT_CFWS_PERSONAL_NAMES = extract;
-		resetPatterns();
-		return this;
-	}
-
-	/**
-	 * Allows &quot;[&quot; or &quot;]&quot; to appear in atext.
-	 * The address:
-	 * <ul><li><code>[Kayaks] &lt;kayaks@kayaks.org&gt;</code></li></ul>
-	 * <p>
-	 * ...is not valid. It should be:
-	 * <p>
-	 * <ul><li><code>&quot;[Kayaks]&quot; &lt;kayaks@kayaks.org&gt;</code></li></ul>
-	 * <p>
-	 * If this boolean is set to false, the parser will act per 2822 and will require
-	 * the quotes; if set to true, it will allow them to be missing.
-	 * <p>
-	 * Use at your own risk. There may be some issue with enabling this feature in conjunction
-	 * with {@link #allowDomainLiterals(boolean)}.
-	 */
-	public RFC2822AddressParser allowSquareBracketsInAtext(final boolean allow) {
-		ALLOW_SQUARE_BRACKETS_IN_ATEXT = allow;
-		resetPatterns();
-		return this;
-	}
-
-	/**
-	 * Allows &quot;)&quot; or &quot;(&quot; to appear in quoted versions of
-	 * the localpart (they are never allowed in unquoted versions).
-	 * The default (2822) behavior is to allow this, i.e. boolean true.
-	 * You can disallow it, but better to leave it true.
-	 */
-	public RFC2822AddressParser allowParentheseInLocalpart(final boolean allow) {
-		ALLOW_PARENS_IN_LOCALPART = allow;
-		resetPatterns();
-		return this;
-	}
-
-	// ---------------------------------------------------------------- parse
-
-	/**
-	 * Parsed message address and various information.
-	 */
-	public static class ParsedAddress {
-		private final boolean isValid;
-		private final String personalName;
-		private final String localPart;
-		private final String domain;
-		private final InternetAddress internetAddress;
-		private final boolean validReturnPath;
-		private final String returnPathAddress;
-
-		private ParsedAddress(
-			final boolean isValid,
-			final String personalName,
-			final String localPart,
-			final String domain,
-			final InternetAddress internetAddress,
-			final boolean validReturnPath,
-			final String returnPathAddress) {
-
-			this.isValid = isValid;
-			this.personalName = personalName;
-			this.internetAddress = internetAddress;
-			this.domain = domain;
-			this.localPart = localPart;
-			this.validReturnPath = validReturnPath;
-			this.returnPathAddress = returnPathAddress;
-		}
-
-		/**
-		 * Returns {@code true} if email is valid.
-		 */
-		public boolean isValid() {
-			return isValid;
-		}
-
-		/**
-		 * Returns personal name. Returned string does
-		 * not reflect any decoding of RFC-2047 encoded personal names.
-		 */
-		public String getPersonalName() {
-			return personalName;
-		}
-
-		/**
-		 * Returns local part of the email address.
-		 */
-		public String getLocalPart() {
-			return localPart;
-		}
-
-		/**
-		 * Returns domain part of the email address.
-		 */
-		public String getDomain() {
-			return domain;
-		}
-
-		/**
-		 * Given a 2822-valid single address string, returns an InternetAddress object holding
-		 * that address, otherwise returns null. The email address that comes back from the
-		 * resulting InternetAddress object's getAddress() call will have comments and unnecessary
-		 * quotation marks or whitespace removed.
-		 */
-		public InternetAddress getInternetAddress() {
-			return internetAddress;
-		}
-
-		/**
-		 * Returns {@code true} if the email represents a valid return path.
-		 */
-		public boolean isValidReturnPath() {
-			return validReturnPath;
-		}
-
-		/**
-		 * Pulls out the cleaned-up return path address. May return an empty string.
-		 * Returns null if there are any syntax issues or other weirdness, otherwise
-		 * the valid, trimmed return path email address without CFWS, surrounding angle brackets,
-		 * with quotes stripped where possible, etc. (may return an empty string).
-		 */
-		public String getReturnPathAddress() {
-			return returnPathAddress;
-		}
-	}
-
-	/**
-	 * Parses email address. Returns {@link ParsedAddress parsed address}, that might be valid or not.
-	 */
-	public ParsedAddress parse(String email) {
-		email = email.trim();
-
-		// match all
-
-		final Matcher mailboxMatcher = MAILBOX_PATTERN().matcher(email);
-		final boolean mailboxMatcherMatches = mailboxMatcher.matches();
-		final String[] mailboxMatcherParts = mailboxMatcherMatches ? _calcMatcherParts(mailboxMatcher) : null;
-
-		final Matcher returnPathMatcher = RETURN_PATH_PATTERN().matcher(email);
-		final boolean returnPathMatches = returnPathMatcher.matches();
-
-		// extract
-
-		String personalName = null;
-		String localPart = null;
-		String domain = null;
-		InternetAddress internetAddress = null;
-		String returnPathAddress = null;
-
-		if (mailboxMatcherMatches) {
-			personalName = mailboxMatcherParts[0];
-			localPart = mailboxMatcherParts[1];
-			domain = mailboxMatcherParts[2];
-			internetAddress = pullFromGroups(mailboxMatcher);
-		}
-
-		if (returnPathMatches) {
-			if (internetAddress != null) {
-				returnPathAddress = internetAddress.getAddress();
-			} else {
-				returnPathAddress = StringPool.EMPTY;
-			}
-		}
-
-		return new ParsedAddress(mailboxMatcherMatches, personalName, localPart, domain, internetAddress, returnPathMatches, returnPathAddress);
-	}
-
-	/**
-	 * Convenient shortcut of {@link #parse(String)} that returns {@code InternetAddress} or {@code null}.
-	 */
-	public InternetAddress parseToInternetAddress(final String email) {
-		final ParsedAddress parsedAddress = parse(email);
-
-		if (!parsedAddress.isValid()) {
-			return null;
-		}
-
-		return parsedAddress.getInternetAddress();
-	}
-
-	/**
-	 * Convenient shortcut of {@link #parse(String)} that returns {@link EmailAddress} or {@code null}.
-	 */
-	public EmailAddress parseToEmailAddress(final String email) {
-		final ParsedAddress parsedAddress = parse(email);
-
-		if (!parsedAddress.isValid()) {
-			return null;
-		}
-
-		return new EmailAddress(parsedAddress.getPersonalName(), parsedAddress.getLocalPart() + '@' + parsedAddress.getDomain());
-	}
-
-	// ---------------------------------------------------------------- regexp
-
-	private Pattern _MAILBOX_PATTERN;
-	private Pattern _RETURN_PATH_PATTERN;
-
-	private Pattern _ADDR_SPEC_PATTERN;        // internal
-	private Pattern _COMMENT_PATTERN;        // internal
-	private Pattern _QUOTED_STRING_WO_CFWS_PATTERN;  // internal
-
-	private Pattern MAILBOX_PATTERN() {
-		if (_MAILBOX_PATTERN == null) {
-			buildPatterns();
-		}
-		return _MAILBOX_PATTERN;
-	}
-
-	private Pattern RETURN_PATH_PATTERN() {
-		if (_RETURN_PATH_PATTERN == null) {
-			buildPatterns();
-		}
-		return _RETURN_PATH_PATTERN;
-	}
-
-	/**
-	 * Resets patterns so they can be build on next use.
-	 */
-	private void resetPatterns() {
-		_MAILBOX_PATTERN = null;
-		_RETURN_PATH_PATTERN = null;
-	}
+    /**
+     * Strict parser.
+     */
+    public static final RFC2822AddressParser STRICT =
+            new RFC2822AddressParser()
+                    .allowDomainLiterals(true)
+                    .allowQuotedIdentifiers(true)
+                    .allowDotInAtext(false)
+                    .extractCfwsPersonalName(true)
+                    .allowSquareBracketsInAtext(false)
+                    .allowParentheseInLocalpart(true);
+    /**
+     * Loose parser.
+     */
+    public static final RFC2822AddressParser LOOSE = new RFC2822AddressParser();
+    private static final Pattern ESCAPED_QUOTE_PATTERN = Pattern.compile("\\\\\"");
+    private static final Pattern ESCAPED_BSLASH_PATTERN = Pattern.compile("\\\\\\\\");
+    private boolean ALLOW_DOMAIN_LITERALS = false;
+    private boolean ALLOW_QUOTED_IDENTIFIERS = true;
+    private boolean ALLOW_DOT_IN_ATEXT = false;
+    private boolean EXTRACT_CFWS_PERSONAL_NAMES = true;
+    private boolean ALLOW_SQUARE_BRACKETS_IN_ATEXT = false;
+    private boolean ALLOW_PARENS_IN_LOCALPART = true;
+    private Pattern _MAILBOX_PATTERN;
+    private Pattern _RETURN_PATH_PATTERN;
+    private Pattern _ADDR_SPEC_PATTERN;        // internal
+    private Pattern _COMMENT_PATTERN;        // internal
+
+    // ---------------------------------------------------------------- parse
+    private Pattern _QUOTED_STRING_WO_CFWS_PATTERN;  // internal
+
+    /**
+     * If the string starts and ends with start and end char, remove them,
+     * otherwise return the string as it was passed in.
+     */
+    private static String removeAnyBounding(final char s, final char e, final String str) {
+        if (str == null || str.length() < 2) {
+            return str;
+        }
+
+        if (str.startsWith(String.valueOf(s)) && str.endsWith(String.valueOf(e))) {
+            return str.substring(1, str.length() - 1);
+        }
+
+        return str;
+    }
+
+    /**
+     * Changes the behavior of the domain parsing. If {@code true}, the parser will
+     * allow 2822 domains, which include single-level domains (e.g. bob@localhost) as well
+     * as domain literals, e.g.:
+     * <p>
+     * <ul>
+     * <li><code>someone@[192.168.1.100]</code> or</li>
+     * <li><code>john.doe@[23:33:A2:22:16:1F]</code> or</li>
+     * <li><code>me@[my computer]</code></li>
+     * </ul>
+     * <p>
+     * The RFC says these are valid email addresses, but many don't like
+     * allowing them. If you don't want to allow them, and only want to allow valid domain names
+     * (<a href="http://www.ietf.org/rfc/rfc1035.txt">RFC 1035</a>, x.y.z.com, etc),
+     * and specifically only those with at least two levels ("example.com"), then
+     * set this flag to {@code false}.
+     */
+    public RFC2822AddressParser allowDomainLiterals(final boolean allow) {
+        ALLOW_DOMAIN_LITERALS = allow;
+        resetPatterns();
+        return this;
+    }
+
+    /**
+     * Defines if quoted identifiers are allowed.
+     * Using quotes and angle brackets around the raw address may be allowed, e.g.:
+     * <p>
+     * <ul>
+     * <li><code>"John Smith" &lt;john.smith@somewhere.com&gt;</code></li>
+     * </ul>
+     * <p>
+     * The RFC says this is a valid mailbox. If you don't want to
+     * allow this, because for example, you only want users to enter in
+     * a raw address (<code>john.smith@somewhere.com</code> - no quotes or angle
+     * brackets), then set the flag {@code false}.
+     */
+    public RFC2822AddressParser allowQuotedIdentifiers(final boolean allow) {
+        ALLOW_QUOTED_IDENTIFIERS = allow;
+        resetPatterns();
+        return this;
+    }
+
+    // ---------------------------------------------------------------- regexp
+
+    /**
+     * Allows &quot;.&quot; to appear in atext (note: only atext which appears
+     * in the 2822 &quot;name-addr&quot; part of the address, not the other instances).
+     * <p>
+     * The addresses:
+     * <ul>
+     * <li><code>Kayaks.org &lt;kayaks@kayaks.org&gt;</code></li>
+     * <li><code>Bob K. Smith&lt;bobksmith@bob.net&gt;</code></li>
+     * </ul>
+     * ...are not valid. They should be:
+     * <ul>
+     * <li><code>&quot;Kayaks.org&quot; &lt;kayaks@kayaks.org&gt;</code></li>
+     * <li><code>&quot;Bob K. Smith&quot; &lt;bobksmith@bob.net&gt;</code></li>
+     * </ul>
+     * If this boolean is set to false, the parser will act per 2822 and will require
+     * the quotes; if set to true, it will allow the use of &quot;.&quot; without quotes.
+     */
+    public RFC2822AddressParser allowDotInAtext(final boolean allow) {
+        ALLOW_DOT_IN_ATEXT = allow;
+        resetPatterns();
+        return this;
+    }
+
+    /**
+     * Controls the behavior of getInternetAddress. If true, allows the real world practice of:
+     * <ul>
+     * <li>&lt;bob@example.com&gt; (Bob Smith)</li>
+     * </ul>
+     * <p>
+     * In this case, &quot;Bob Smith&quot; is not technically the personal name, just a
+     * comment. If this is set to true, the methods will convert this into:
+     * <ul>
+     * <li>Bob Smith &lt;bob@example.com&gt;</li>
+     * </ul>
+     * <p>
+     * This also happens somewhat more often and appropriately with
+     * <code>mailer-daemon@blah.com (Mail Delivery System)</code>.
+     * <p>
+     * <p>
+     * If a personal name appears to the left and CFWS appears to the right of an address,
+     * the methods will favor the personal name to the left. If the methods need to use the
+     * CFWS following the address, they will take the first comment token they find.
+     */
+    public RFC2822AddressParser extractCfwsPersonalName(final boolean extract) {
+        EXTRACT_CFWS_PERSONAL_NAMES = extract;
+        resetPatterns();
+        return this;
+    }
+
+    /**
+     * Allows &quot;[&quot; or &quot;]&quot; to appear in atext.
+     * The address:
+     * <ul><li><code>[Kayaks] &lt;kayaks@kayaks.org&gt;</code></li></ul>
+     * <p>
+     * ...is not valid. It should be:
+     * <p>
+     * <ul><li><code>&quot;[Kayaks]&quot; &lt;kayaks@kayaks.org&gt;</code></li></ul>
+     * <p>
+     * If this boolean is set to false, the parser will act per 2822 and will require
+     * the quotes; if set to true, it will allow them to be missing.
+     * <p>
+     * Use at your own risk. There may be some issue with enabling this feature in conjunction
+     * with {@link #allowDomainLiterals(boolean)}.
+     */
+    public RFC2822AddressParser allowSquareBracketsInAtext(final boolean allow) {
+        ALLOW_SQUARE_BRACKETS_IN_ATEXT = allow;
+        resetPatterns();
+        return this;
+    }
+
+    /**
+     * Allows &quot;)&quot; or &quot;(&quot; to appear in quoted versions of
+     * the localpart (they are never allowed in unquoted versions).
+     * The default (2822) behavior is to allow this, i.e. boolean true.
+     * You can disallow it, but better to leave it true.
+     */
+    public RFC2822AddressParser allowParentheseInLocalpart(final boolean allow) {
+        ALLOW_PARENS_IN_LOCALPART = allow;
+        resetPatterns();
+        return this;
+    }
+
+    /**
+     * Parses email address. Returns {@link ParsedAddress parsed address}, that might be valid or not.
+     */
+    public ParsedAddress parse(String email) {
+        email = email.trim();
+
+        // match all
+
+        final Matcher mailboxMatcher = MAILBOX_PATTERN().matcher(email);
+        final boolean mailboxMatcherMatches = mailboxMatcher.matches();
+        final String[] mailboxMatcherParts = mailboxMatcherMatches ? _calcMatcherParts(mailboxMatcher) : null;
+
+        final Matcher returnPathMatcher = RETURN_PATH_PATTERN().matcher(email);
+        final boolean returnPathMatches = returnPathMatcher.matches();
+
+        // extract
+
+        String personalName = null;
+        String localPart = null;
+        String domain = null;
+        InternetAddress internetAddress = null;
+        String returnPathAddress = null;
+
+        if (mailboxMatcherMatches) {
+            personalName = mailboxMatcherParts[0];
+            localPart = mailboxMatcherParts[1];
+            domain = mailboxMatcherParts[2];
+            internetAddress = pullFromGroups(mailboxMatcher);
+        }
+
+        if (returnPathMatches) {
+            if (internetAddress != null) {
+                returnPathAddress = internetAddress.getAddress();
+            } else {
+                returnPathAddress = StringPool.EMPTY;
+            }
+        }
+
+        return new ParsedAddress(mailboxMatcherMatches, personalName, localPart, domain, internetAddress, returnPathMatches, returnPathAddress);
+    }
+
+    /**
+     * Convenient shortcut of {@link #parse(String)} that returns {@code InternetAddress} or {@code null}.
+     */
+    public InternetAddress parseToInternetAddress(final String email) {
+        final ParsedAddress parsedAddress = parse(email);
+
+        if (!parsedAddress.isValid()) {
+            return null;
+        }
+
+        return parsedAddress.getInternetAddress();
+    }
+
+    /**
+     * Convenient shortcut of {@link #parse(String)} that returns {@link EmailAddress} or {@code null}.
+     */
+    public EmailAddress parseToEmailAddress(final String email) {
+        final ParsedAddress parsedAddress = parse(email);
+
+        if (!parsedAddress.isValid()) {
+            return null;
+        }
+
+        return new EmailAddress(parsedAddress.getPersonalName(), parsedAddress.getLocalPart() + '@' + parsedAddress.getDomain());
+    }
+
+    private Pattern MAILBOX_PATTERN() {
+        if (_MAILBOX_PATTERN == null) {
+            buildPatterns();
+        }
+        return _MAILBOX_PATTERN;
+    }
+
+    private Pattern RETURN_PATH_PATTERN() {
+        if (_RETURN_PATH_PATTERN == null) {
+            buildPatterns();
+        }
+        return _RETURN_PATH_PATTERN;
+    }
+
+    /**
+     * Resets patterns so they can be build on next use.
+     */
+    private void resetPatterns() {
+        _MAILBOX_PATTERN = null;
+        _RETURN_PATH_PATTERN = null;
+    }
+
+    /**
+     * Builds all regexp patterns.
+     */
+    private void buildPatterns() {
+
+        // http://tools.ietf.org/html/rfc2822
 
-	/**
-	 * Builds all regexp patterns.
-	 */
-	private void buildPatterns() {
+        // RFC 2822 2.2.2 Structured Header Field Bodies
 
-		// http://tools.ietf.org/html/rfc2822
+        final String CRLF = "\\r\\n";
+        final String WSP = "[ \\t]";
+        final String FWSP = "(?:" + WSP + "*" + CRLF + ")?" + WSP + "+";
 
-		// RFC 2822 2.2.2 Structured Header Field Bodies
+        // RFC 2822 3.2.1 Primitive tokens
 
-		final String CRLF = "\\r\\n";
-		final String WSP = "[ \\t]";
-		final String FWSP = "(?:" + WSP + "*" + CRLF + ")?" + WSP + "+";
+        final String D_QUOTE = "\\\"";
+        final String NO_WS_CTL = "\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F";
+        final String ASCII_TEXT = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]";
 
-		// RFC 2822 3.2.1 Primitive tokens
+        // RFC 2822 3.2.2 Quoted characters
 
-		final String D_QUOTE = "\\\"";
-		final String NO_WS_CTL = "\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F";
-		final String ASCII_TEXT = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]";
+        final String QUOTED_PAIR = "(?:\\\\" + ASCII_TEXT + ")";
 
-		// RFC 2822 3.2.2 Quoted characters
+        // RFC 2822 3.2.3 CFWS specification
 
-		final String QUOTED_PAIR = "(?:\\\\" + ASCII_TEXT + ")";
+        final String C_TEXT = "[" + NO_WS_CTL + "\\!-\\'\\*-\\[\\]-\\~]";
+        final String C_CONTENT = C_TEXT + "|" + QUOTED_PAIR; // + "|" + comment;
+        final String COMMENT = "\\((?:(?:" + FWSP + ")?" + C_CONTENT + ")*(?:" + FWSP + ")?\\)";
+        final String CFWS = "(?:(?:" + FWSP + ")?" + COMMENT + ")*(?:(?:(?:" + FWSP + ")?" + COMMENT + ")|(?:" + FWSP + "))";
 
-		// RFC 2822 3.2.3 CFWS specification
+        // RFC 2822 3.2.4 Atom
 
-		final String C_TEXT = "[" + NO_WS_CTL + "\\!-\\'\\*-\\[\\]-\\~]";
-		final String C_CONTENT = C_TEXT + "|" + QUOTED_PAIR; // + "|" + comment;
-		final String COMMENT = "\\((?:(?:" + FWSP + ")?" + C_CONTENT + ")*(?:" + FWSP + ")?\\)";
-		final String CFWS = "(?:(?:" + FWSP + ")?" + COMMENT + ")*(?:(?:(?:" + FWSP + ")?" + COMMENT + ")|(?:" + FWSP + "))";
+        final String A_TEXT =
+                "[a-zA-Z0-9\\!\\#-\\'\\*\\+\\-\\/\\=\\?\\^-\\`\\{-\\~"
+                        + (ALLOW_DOT_IN_ATEXT ? "\\." : "")
+                        + (ALLOW_SQUARE_BRACKETS_IN_ATEXT ? "\\[\\]" : "") + "]";
+        final String REGULAR_A_TEXT = "[a-zA-Z0-9\\!\\#-\\'\\*\\+\\-\\/\\=\\?\\^-\\`\\{-\\~]";
 
-		// RFC 2822 3.2.4 Atom
+        final String ATOM = "(?:" + CFWS + ")?" + A_TEXT + "+" + "(?:" + CFWS + ")?";
+        final String DOT_ATOM_TEXT = REGULAR_A_TEXT + "+" + "(?:" + "\\." + REGULAR_A_TEXT + "+)*";
+        final String CAP_DOT_ATOM_NO_CFWS = "(?:" + CFWS + ")?(" + DOT_ATOM_TEXT + ")(?:" + CFWS + ")?";
+        final String CAP_DOT_ATOM_TRAILING_CFWS = "(?:" + CFWS + ")?(" + DOT_ATOM_TEXT + ")(" + CFWS + ")?";
 
-		final String A_TEXT =
-			"[a-zA-Z0-9\\!\\#-\\'\\*\\+\\-\\/\\=\\?\\^-\\`\\{-\\~"
-				+ (ALLOW_DOT_IN_ATEXT ? "\\." : "")
-				+ (ALLOW_SQUARE_BRACKETS_IN_ATEXT ? "\\[\\]" : "") + "]";
-		final String REGULAR_A_TEXT = "[a-zA-Z0-9\\!\\#-\\'\\*\\+\\-\\/\\=\\?\\^-\\`\\{-\\~]";
+        // RFC 2822 3.2.5 Quoted strings
 
-		final String ATOM = "(?:" + CFWS + ")?" + A_TEXT + "+" + "(?:" + CFWS + ")?";
-		final String DOT_ATOM_TEXT = REGULAR_A_TEXT + "+" + "(?:" + "\\." + REGULAR_A_TEXT + "+)*";
-		final String CAP_DOT_ATOM_NO_CFWS = "(?:" + CFWS + ")?(" + DOT_ATOM_TEXT + ")(?:" + CFWS + ")?";
-		final String CAP_DOT_ATOM_TRAILING_CFWS = "(?:" + CFWS + ")?(" + DOT_ATOM_TEXT + ")(" + CFWS + ")?";
+        final String Q_TEXT = "[" + NO_WS_CTL + "\\!\\#-\\[\\]-\\~]";
+        final String LOCAL_PART_Q_TEXT = "[" + NO_WS_CTL + (ALLOW_PARENS_IN_LOCALPART ? "\\!\\#-\\[\\]-\\~]" : "\\!\\#-\\'\\*-\\[\\]-\\~]");
 
-		// RFC 2822 3.2.5 Quoted strings
+        final String Q_CONTENT = "(?:" + Q_TEXT + "|" + QUOTED_PAIR + ")";
+        final String LOCAL_PART_Q_CONTENT = "(?>" + LOCAL_PART_Q_TEXT + "|" + QUOTED_PAIR + ")";
+        final String QUOTED_STRING_WOCFWS = D_QUOTE + "(?>(?:" + FWSP + ")?" + Q_CONTENT + ")*(?:" + FWSP + ")?" + D_QUOTE;
+        final String QUOTED_STRING = "(?:" + CFWS + ")?" + QUOTED_STRING_WOCFWS + "(?:" + CFWS + ")?";
+        final String LOCAL_PART_QUOTED_STRING = "(?:" + CFWS + ")?(" + D_QUOTE + "(?:(?:" + FWSP + ")?" + LOCAL_PART_Q_CONTENT + ")*(?:" + FWSP + ")?" + D_QUOTE + ")(?:" + CFWS + ")?";
 
-		final String Q_TEXT = "[" + NO_WS_CTL + "\\!\\#-\\[\\]-\\~]";
-		final String LOCAL_PART_Q_TEXT = "[" + NO_WS_CTL + (ALLOW_PARENS_IN_LOCALPART ? "\\!\\#-\\[\\]-\\~]" : "\\!\\#-\\'\\*-\\[\\]-\\~]");
+        // RFC 2822 3.2.6 Miscellaneous tokens
 
-		final String Q_CONTENT = "(?:" + Q_TEXT + "|" + QUOTED_PAIR + ")";
-		final String LOCAL_PART_Q_CONTENT = "(?>" + LOCAL_PART_Q_TEXT + "|" + QUOTED_PAIR + ")";
-		final String QUOTED_STRING_WOCFWS = D_QUOTE + "(?>(?:" + FWSP + ")?" + Q_CONTENT + ")*(?:" + FWSP + ")?" + D_QUOTE;
-		final String QUOTED_STRING = "(?:" + CFWS + ")?" + QUOTED_STRING_WOCFWS + "(?:" + CFWS + ")?";
-		final String LOCAL_PART_QUOTED_STRING = "(?:" + CFWS + ")?(" + D_QUOTE + "(?:(?:" + FWSP + ")?" + LOCAL_PART_Q_CONTENT + ")*(?:" + FWSP + ")?" + D_QUOTE + ")(?:" + CFWS + ")?";
+        final String WORD = "(?:(?:" + ATOM + ")|(?:" + QUOTED_STRING + "))";
 
-		// RFC 2822 3.2.6 Miscellaneous tokens
+        // by 2822: phrase = 1*word / obs-phrase
+        // implemented here as: phrase = word (FWS word)*
+        // so that aaaa can't be four words, which can cause tons of recursive backtracking
 
-		final String WORD = "(?:(?:" + ATOM + ")|(?:" + QUOTED_STRING + "))";
+        final String PHRASE = WORD + "(?:(?:" + FWSP + ")" + WORD + ")*";
 
-		// by 2822: phrase = 1*word / obs-phrase
-		// implemented here as: phrase = word (FWS word)*
-		// so that aaaa can't be four words, which can cause tons of recursive backtracking
+        // RFC 1035 tokens for domain names
 
-		final String PHRASE = WORD + "(?:(?:" + FWSP + ")" + WORD + ")*";
+        final String LETTER = "[a-zA-Z]";
+        final String LET_DIG = "[a-zA-Z0-9]";
+        final String LET_DIG_HYP = "[a-zA-Z0-9-]";
+        final String RFC_LABEL = LET_DIG + "(?:" + LET_DIG_HYP + "{0,61}" + LET_DIG + ")?";
+        final String RFC_1035_DOMAIN_NAME = RFC_LABEL + "(?:\\." + RFC_LABEL + ")*\\." + LETTER + "{2,6}";
 
-		// RFC 1035 tokens for domain names
+        // RFC 2822 3.4 Address specification
 
-		final String LETTER = "[a-zA-Z]";
-		final String LET_DIG = "[a-zA-Z0-9]";
-		final String LET_DIG_HYP = "[a-zA-Z0-9-]";
-		final String RFC_LABEL = LET_DIG + "(?:" + LET_DIG_HYP + "{0,61}" + LET_DIG + ")?";
-		final String RFC_1035_DOMAIN_NAME = RFC_LABEL + "(?:\\." + RFC_LABEL + ")*\\." + LETTER + "{2,6}";
+        final String D_TEXT = "[" + NO_WS_CTL + "\\!-Z\\^-\\~]";
 
-		// RFC 2822 3.4 Address specification
+        final String D_CONTENT = D_TEXT + "|" + QUOTED_PAIR;
+        final String CAP_DOMAIN_LITERAL_NO_CFWS = "(?:" + CFWS + ")?" + "(\\[" + "(?:(?:" + FWSP + ")?(?:" + D_CONTENT + ")+)*(?:" + FWSP + ")?\\])" + "(?:" + CFWS + ")?";
+        final String CAP_DOMAIN_LITERAL_TRAILING_CFWS = "(?:" + CFWS + ")?" + "(\\[" + "(?:(?:" + FWSP + ")?(?:" + D_CONTENT + ")+)*(?:" + FWSP + ")?\\])" + "(" + CFWS + ")?";
+        final String RFC_2822_DOMAIN = "(?:" + CAP_DOT_ATOM_NO_CFWS + "|" + CAP_DOMAIN_LITERAL_NO_CFWS + ")";
+        final String CAP_CFWSR_FC2822_DOMAIN = "(?:" + CAP_DOT_ATOM_TRAILING_CFWS + "|" + CAP_DOMAIN_LITERAL_TRAILING_CFWS + ")";
 
-		final String D_TEXT = "[" + NO_WS_CTL + "\\!-Z\\^-\\~]";
+        final String DOMAIN = ALLOW_DOMAIN_LITERALS ? RFC_2822_DOMAIN : "(?:" + CFWS + ")?(" + RFC_1035_DOMAIN_NAME + ")(?:" + CFWS + ")?";
+        final String CAP_CFWS_DOMAIN = ALLOW_DOMAIN_LITERALS ? CAP_CFWSR_FC2822_DOMAIN : "(?:" + CFWS + ")?(" + RFC_1035_DOMAIN_NAME + ")(" + CFWS + ")?";
+        final String LOCAL_PART = "(" + CAP_DOT_ATOM_NO_CFWS + "|" + LOCAL_PART_QUOTED_STRING + ")";
 
-		final String D_CONTENT = D_TEXT + "|" + QUOTED_PAIR;
-		final String CAP_DOMAIN_LITERAL_NO_CFWS = "(?:" + CFWS + ")?" + "(\\[" + "(?:(?:" + FWSP + ")?(?:" + D_CONTENT + ")+)*(?:" + FWSP + ")?\\])" + "(?:" + CFWS + ")?";
-		final String CAP_DOMAIN_LITERAL_TRAILING_CFWS = "(?:" + CFWS + ")?" + "(\\[" + "(?:(?:" + FWSP + ")?(?:" + D_CONTENT + ")+)*(?:" + FWSP + ")?\\])" + "(" + CFWS + ")?";
-		final String RFC_2822_DOMAIN = "(?:" + CAP_DOT_ATOM_NO_CFWS + "|" + CAP_DOMAIN_LITERAL_NO_CFWS + ")";
-		final String CAP_CFWSR_FC2822_DOMAIN = "(?:" + CAP_DOT_ATOM_TRAILING_CFWS + "|" + CAP_DOMAIN_LITERAL_TRAILING_CFWS + ")";
+        // uniqueAddrSpec exists so we can have a duplicate tree that has a capturing group
+        // instead of a non-capturing group for the trailing CFWS after the domain token
+        // that we wouldn't want if it was inside
+        // an angleAddr. The matching should be otherwise identical.
 
-		final String DOMAIN = ALLOW_DOMAIN_LITERALS ? RFC_2822_DOMAIN : "(?:" + CFWS + ")?(" + RFC_1035_DOMAIN_NAME + ")(?:" + CFWS + ")?";
-		final String CAP_CFWS_DOMAIN = ALLOW_DOMAIN_LITERALS ? CAP_CFWSR_FC2822_DOMAIN : "(?:" + CFWS + ")?(" + RFC_1035_DOMAIN_NAME + ")(" + CFWS + ")?";
-		final String LOCAL_PART = "(" + CAP_DOT_ATOM_NO_CFWS + "|" + LOCAL_PART_QUOTED_STRING + ")";
+        final String ADDR_SPEC = LOCAL_PART + "@" + DOMAIN;
+        final String UNIQUE_ADDR_SPEC = LOCAL_PART + "@" + CAP_CFWS_DOMAIN;
+        final String ANGLE_ADDR = "(?:" + CFWS + ")?<" + ADDR_SPEC + ">(" + CFWS + ")?";
 
-		// uniqueAddrSpec exists so we can have a duplicate tree that has a capturing group
-		// instead of a non-capturing group for the trailing CFWS after the domain token
-		// that we wouldn't want if it was inside
-		// an angleAddr. The matching should be otherwise identical.
+        final String NAME_ADDR = "(" + PHRASE + ")??(" + ANGLE_ADDR + ")";
+        final String MAIL_BOX = (ALLOW_QUOTED_IDENTIFIERS ? "(" + NAME_ADDR + ")|" : "") + "(" + UNIQUE_ADDR_SPEC + ")";
 
-		final String ADDR_SPEC = LOCAL_PART + "@" + DOMAIN;
-		final String UNIQUE_ADDR_SPEC = LOCAL_PART + "@" + CAP_CFWS_DOMAIN;
-		final String ANGLE_ADDR = "(?:" + CFWS + ")?<" + ADDR_SPEC + ">(" + CFWS + ")?";
+        final String RETURN_PATH = "(?:(?:" + CFWS + ")?<((?:" + CFWS + ")?|" + ADDR_SPEC + ")>(?:" + CFWS + ")?)";
 
-		final String NAME_ADDR = "(" + PHRASE + ")??(" + ANGLE_ADDR + ")";
-		final String MAIL_BOX = (ALLOW_QUOTED_IDENTIFIERS ? "(" + NAME_ADDR + ")|" : "") + "(" + UNIQUE_ADDR_SPEC + ")";
+        // private static final String mailboxList = "(?:(?:" + mailbox + ")(?:,(?:" + mailbox + "))*)";
+        // private static final String groupPostfix = "(?:" + CFWS + "|(?:" + mailboxList + ")" + ")?;(?:" + CFWS + ")?";
+        // private static final String groupPrefix = phrase + ":";
+        // private static final String group = groupPrefix + groupPostfix;
+        // private static final String address = "(?:(?:" + mailbox + ")|(?:" + group + "))"
 
-		final String RETURN_PATH = "(?:(?:" + CFWS + ")?<((?:" + CFWS + ")?|" + ADDR_SPEC + ")>(?:" + CFWS + ")?)";
 
-		//private static final String mailboxList = "(?:(?:" + mailbox + ")(?:,(?:" + mailbox + "))*)";
-		//private static final String groupPostfix = "(?:" + CFWS + "|(?:" + mailboxList + ")" + ")?;(?:" + CFWS + ")?";
-		//private static final String groupPrefix = phrase + ":";
-		//private static final String group = groupPrefix + groupPostfix;
-		//private static final String address = "(?:(?:" + mailbox + ")|(?:" + group + "))"
+        // Java regex pattern for 2822
+        _MAILBOX_PATTERN = Pattern.compile(MAIL_BOX);
 
+        _ADDR_SPEC_PATTERN = Pattern.compile(ADDR_SPEC);
+        // final Pattern MAILBOX_LIST_PATTERN = Pattern.compile(mailboxList);
+        _COMMENT_PATTERN = Pattern.compile(COMMENT);
 
-		// Java regex pattern for 2822
-		_MAILBOX_PATTERN = Pattern.compile(MAIL_BOX);
+        _QUOTED_STRING_WO_CFWS_PATTERN = Pattern.compile(QUOTED_STRING_WOCFWS);
+        _RETURN_PATH_PATTERN = Pattern.compile(RETURN_PATH);
+    }
 
-		_ADDR_SPEC_PATTERN = Pattern.compile(ADDR_SPEC);
-		//final Pattern MAILBOX_LIST_PATTERN = Pattern.compile(mailboxList);
-		_COMMENT_PATTERN = Pattern.compile(COMMENT);
 
-		_QUOTED_STRING_WO_CFWS_PATTERN = Pattern.compile(QUOTED_STRING_WOCFWS);
-		_RETURN_PATH_PATTERN = Pattern.compile(RETURN_PATH);
-	}
+    // ---------------------------------------------------------------- utilities
 
-	private static final Pattern ESCAPED_QUOTE_PATTERN = Pattern.compile("\\\\\"");
-	private static final Pattern ESCAPED_BSLASH_PATTERN = Pattern.compile("\\\\\\\\");
+    private InternetAddress pullFromGroups(final Matcher m) {
+        InternetAddress currentInternetAddress;
+        final String[] parts = _calcMatcherParts(m);
 
+        if (parts[1] == null || parts[2] == null) {
+            return null;
+        }
 
-	// ---------------------------------------------------------------- utilities
+        // if for some reason you want to require that the result be re-parsable by
+        // InternetAddress, you
+        // could uncomment the appropriate stuff below, but note that not all the utility
+        // functions use pullFromGroups; some call getMatcherParts directly.
+        try {
+            // currentInternetAddress = new InternetAddress(parts[0] + " <" + parts[1] + "@" +
+            //                                 parts[2]+ ">", true);
+            // so it parses it OK, but since javamail doesn't extract too well
+            // we make sure that the consistent parts
+            // are correct
 
-	private InternetAddress pullFromGroups(final Matcher m) {
-		InternetAddress currentInternetAddress;
-		final String[] parts = _calcMatcherParts(m);
-
-		if (parts[1] == null || parts[2] == null) {
-			return null;
-		}
-
-		// if for some reason you want to require that the result be re-parsable by
-		// InternetAddress, you
-		// could uncomment the appropriate stuff below, but note that not all the utility
-		// functions use pullFromGroups; some call getMatcherParts directly.
-		try {
-			//currentInternetAddress = new InternetAddress(parts[0] + " <" + parts[1] + "@" +
-			//                                 parts[2]+ ">", true);
-			// so it parses it OK, but since javamail doesn't extract too well
-			// we make sure that the consistent parts
-			// are correct
-
-			currentInternetAddress = new InternetAddress();
-			currentInternetAddress.setPersonal(parts[0]);
-			currentInternetAddress.setAddress(parts[1] + "@" + parts[2]);
-		} catch (final UnsupportedEncodingException uee) {
-			currentInternetAddress = null;
-		}
-
-		return currentInternetAddress;
-	}
-
-	private String[] _calcMatcherParts(final Matcher m) {
-		String currentLocalpart = null;
-		String currentDomainpart = null;
-		final String localPartDa;
-		String localPartQs = null;
-		final String domainPartDa;
-		String domainPartDl = null;
-		String personalString = null;
-
-		// see the group-ID lists in the grammar comments
-
-		if (ALLOW_QUOTED_IDENTIFIERS) {
-			if (ALLOW_DOMAIN_LITERALS) {
-				// yes quoted identifiers, yes domain literals
-
-				if (m.group(1) != null) {
-					// name-addr form
-					localPartDa = m.group(5);
-					if (localPartDa == null) {
-						localPartQs = m.group(6);
-					}
-
-					domainPartDa = m.group(7);
-					if (domainPartDa == null) {
-						domainPartDl = m.group(8);
-					}
-
-					currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
-
-					currentDomainpart = (domainPartDa == null ? domainPartDl : domainPartDa);
-
-					personalString = m.group(2);
-					if (personalString == null && EXTRACT_CFWS_PERSONAL_NAMES) {
-						personalString = m.group(9);
-						personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
-					}
-				} else if (m.group(10) != null) {
-					// addr-spec form
-
-					localPartDa = m.group(12);
-					if (localPartDa == null) {
-						localPartQs = m.group(13);
-					}
-
-					domainPartDa = m.group(14);
-					if (domainPartDa == null) {
-						domainPartDl = m.group(15);
-					}
-
-					currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
-
-					currentDomainpart = (domainPartDa == null ? domainPartDl : domainPartDa);
-
-					if (EXTRACT_CFWS_PERSONAL_NAMES) {
-						personalString = m.group(16);
-						personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
-					}
-				}
-			} else {
-				// yes quoted identifiers, no domain literals
-
-				if (m.group(1) != null) {
-					// name-addr form
-
-					localPartDa = m.group(5);
-					if (localPartDa == null) {
-						localPartQs = m.group(6);
-					}
-
-					currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
-
-					currentDomainpart = m.group(7);
-
-					personalString = m.group(2);
-					if (personalString == null && EXTRACT_CFWS_PERSONAL_NAMES) {
-						personalString = m.group(8);
-						personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
-					}
-				} else if (m.group(9) != null) {
-					// addr-spec form
-
-					localPartDa = m.group(11);
-					if (localPartDa == null) {
-						localPartQs = m.group(12);
-					}
-
-					currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
-
-					currentDomainpart = m.group(13);
-
-					if (EXTRACT_CFWS_PERSONAL_NAMES) {
-						personalString = m.group(14);
-						personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
-					}
-				}
-			}
-		} else {
-			// no quoted identifiers, yes|no domain literals
-
-			localPartDa = m.group(3);
-			if (localPartDa == null) {
-				localPartQs = m.group(4);
-			}
-
-			domainPartDa = m.group(5);
-			if (domainPartDa == null && ALLOW_DOMAIN_LITERALS) {
-				domainPartDl = m.group(6);
-			}
-
-			currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
-
-			currentDomainpart = (domainPartDa == null ? domainPartDl : domainPartDa);
-
-			if (EXTRACT_CFWS_PERSONAL_NAMES) {
-				personalString = m.group((ALLOW_DOMAIN_LITERALS ? 1 : 0) + 6);
-				personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
-			}
-		}
-
-		if (currentLocalpart != null) {
-			currentLocalpart = currentLocalpart.trim();
-		}
-		if (currentDomainpart != null) {
-			currentDomainpart = currentDomainpart.trim();
-		}
-		if (personalString != null) {
-			// trim even though calling cPS which trims, because the latter may return
-			// the same thing back without trimming
-			personalString = personalString.trim();
-			personalString = cleanupPersonalString(personalString);
-		}
-
-		// remove any unnecessary bounding quotes from the localpart:
-
-		final String testAddr = removeAnyBounding('"', '"', currentLocalpart) + "@" + currentDomainpart;
-
-		if (_ADDR_SPEC_PATTERN.matcher(testAddr).matches()) {
-			currentLocalpart = removeAnyBounding('"', '"', currentLocalpart);
-		}
-
-		return (new String[]{personalString, currentLocalpart, currentDomainpart});
-	}
-
-	/**
-	 * Given a string, extract the first matched comment token as defined in 2822, trimmed;
-	 * return null on all errors or non-findings.
-	 * <p>
-	 * Note for future improvement: if COMMENT_PATTERN could handle nested
-	 * comments, then this should be able to as well, but if this method were to be used to
-	 * find the CFWS personal name (see boolean option) then such a nested comment would
-	 * probably not be the one you were looking for?
-	 */
-	private String getFirstComment(final String text) {
-		if (text == null) {
-			return null; // important
-		}
-
-		final Matcher m = _COMMENT_PATTERN.matcher(text);
-
-		if (!m.find()) {
-			return null;
-		}
-
-		return m.group().trim();    // must trim
-	}
-
-	private String cleanupPersonalString(String text) {
-		if (text == null) {
-			return null;
-		}
-		text = text.trim();
-
-		final Matcher m = _QUOTED_STRING_WO_CFWS_PATTERN.matcher(text);
-
-		if (!m.matches()) {
-			return text;
-		}
-
-		text = removeAnyBounding('"', '"', m.group());
-
-		text = ESCAPED_BSLASH_PATTERN.matcher(text).replaceAll("\\\\");
-		text = ESCAPED_QUOTE_PATTERN.matcher(text).replaceAll("\"");
-
-		return text.trim();
-	}
-
-	/**
-	 * If the string starts and ends with start and end char, remove them,
-	 * otherwise return the string as it was passed in.
-	 */
-	private static String removeAnyBounding(final char s, final char e, final String str) {
-		if (str == null || str.length() < 2) {
-			return str;
-		}
-
-		if (str.startsWith(String.valueOf(s)) && str.endsWith(String.valueOf(e))) {
-			return str.substring(1, str.length() - 1);
-		}
-
-		return str;
-	}
+            currentInternetAddress = new InternetAddress();
+            currentInternetAddress.setPersonal(parts[0]);
+            currentInternetAddress.setAddress(parts[1] + "@" + parts[2]);
+        } catch (final UnsupportedEncodingException uee) {
+            currentInternetAddress = null;
+        }
+
+        return currentInternetAddress;
+    }
+
+    private String[] _calcMatcherParts(final Matcher m) {
+        String currentLocalpart = null;
+        String currentDomainpart = null;
+        final String localPartDa;
+        String localPartQs = null;
+        final String domainPartDa;
+        String domainPartDl = null;
+        String personalString = null;
+
+        // see the group-ID lists in the grammar comments
+
+        if (ALLOW_QUOTED_IDENTIFIERS) {
+            if (ALLOW_DOMAIN_LITERALS) {
+                // yes quoted identifiers, yes domain literals
+
+                if (m.group(1) != null) {
+                    // name-addr form
+                    localPartDa = m.group(5);
+                    if (localPartDa == null) {
+                        localPartQs = m.group(6);
+                    }
+
+                    domainPartDa = m.group(7);
+                    if (domainPartDa == null) {
+                        domainPartDl = m.group(8);
+                    }
+
+                    currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
+
+                    currentDomainpart = (domainPartDa == null ? domainPartDl : domainPartDa);
+
+                    personalString = m.group(2);
+                    if (personalString == null && EXTRACT_CFWS_PERSONAL_NAMES) {
+                        personalString = m.group(9);
+                        personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
+                    }
+                } else if (m.group(10) != null) {
+                    // addr-spec form
+
+                    localPartDa = m.group(12);
+                    if (localPartDa == null) {
+                        localPartQs = m.group(13);
+                    }
+
+                    domainPartDa = m.group(14);
+                    if (domainPartDa == null) {
+                        domainPartDl = m.group(15);
+                    }
+
+                    currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
+
+                    currentDomainpart = (domainPartDa == null ? domainPartDl : domainPartDa);
+
+                    if (EXTRACT_CFWS_PERSONAL_NAMES) {
+                        personalString = m.group(16);
+                        personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
+                    }
+                }
+            } else {
+                // yes quoted identifiers, no domain literals
+
+                if (m.group(1) != null) {
+                    // name-addr form
+
+                    localPartDa = m.group(5);
+                    if (localPartDa == null) {
+                        localPartQs = m.group(6);
+                    }
+
+                    currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
+
+                    currentDomainpart = m.group(7);
+
+                    personalString = m.group(2);
+                    if (personalString == null && EXTRACT_CFWS_PERSONAL_NAMES) {
+                        personalString = m.group(8);
+                        personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
+                    }
+                } else if (m.group(9) != null) {
+                    // addr-spec form
+
+                    localPartDa = m.group(11);
+                    if (localPartDa == null) {
+                        localPartQs = m.group(12);
+                    }
+
+                    currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
+
+                    currentDomainpart = m.group(13);
+
+                    if (EXTRACT_CFWS_PERSONAL_NAMES) {
+                        personalString = m.group(14);
+                        personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
+                    }
+                }
+            }
+        } else {
+            // no quoted identifiers, yes|no domain literals
+
+            localPartDa = m.group(3);
+            if (localPartDa == null) {
+                localPartQs = m.group(4);
+            }
+
+            domainPartDa = m.group(5);
+            if (domainPartDa == null && ALLOW_DOMAIN_LITERALS) {
+                domainPartDl = m.group(6);
+            }
+
+            currentLocalpart = (localPartDa == null ? localPartQs : localPartDa);
+
+            currentDomainpart = (domainPartDa == null ? domainPartDl : domainPartDa);
+
+            if (EXTRACT_CFWS_PERSONAL_NAMES) {
+                personalString = m.group((ALLOW_DOMAIN_LITERALS ? 1 : 0) + 6);
+                personalString = removeAnyBounding('(', ')', getFirstComment(personalString));
+            }
+        }
+
+        if (currentLocalpart != null) {
+            currentLocalpart = currentLocalpart.trim();
+        }
+        if (currentDomainpart != null) {
+            currentDomainpart = currentDomainpart.trim();
+        }
+        if (personalString != null) {
+            // trim even though calling cPS which trims, because the latter may return
+            // the same thing back without trimming
+            personalString = personalString.trim();
+            personalString = cleanupPersonalString(personalString);
+        }
+
+        // remove any unnecessary bounding quotes from the localpart:
+
+        final String testAddr = removeAnyBounding('"', '"', currentLocalpart) + "@" + currentDomainpart;
+
+        if (_ADDR_SPEC_PATTERN.matcher(testAddr).matches()) {
+            currentLocalpart = removeAnyBounding('"', '"', currentLocalpart);
+        }
+
+        return (new String[]{personalString, currentLocalpart, currentDomainpart});
+    }
+
+    /**
+     * Given a string, extract the first matched comment token as defined in 2822, trimmed;
+     * return null on all errors or non-findings.
+     * <p>
+     * Note for future improvement: if COMMENT_PATTERN could handle nested
+     * comments, then this should be able to as well, but if this method were to be used to
+     * find the CFWS personal name (see boolean option) then such a nested comment would
+     * probably not be the one you were looking for?
+     */
+    private String getFirstComment(final String text) {
+        if (text == null) {
+            return null; // important
+        }
+
+        final Matcher m = _COMMENT_PATTERN.matcher(text);
+
+        if (!m.find()) {
+            return null;
+        }
+
+        return m.group().trim();    // must trim
+    }
+
+    private String cleanupPersonalString(String text) {
+        if (text == null) {
+            return null;
+        }
+        text = text.trim();
+
+        final Matcher m = _QUOTED_STRING_WO_CFWS_PATTERN.matcher(text);
+
+        if (!m.matches()) {
+            return text;
+        }
+
+        text = removeAnyBounding('"', '"', m.group());
+
+        text = ESCAPED_BSLASH_PATTERN.matcher(text).replaceAll("\\\\");
+        text = ESCAPED_QUOTE_PATTERN.matcher(text).replaceAll("\"");
+
+        return text.trim();
+    }
+
+    /**
+     * Parsed message address and various information.
+     */
+    public static class ParsedAddress {
+        private final boolean isValid;
+        private final String personalName;
+        private final String localPart;
+        private final String domain;
+        private final InternetAddress internetAddress;
+        private final boolean validReturnPath;
+        private final String returnPathAddress;
+
+        private ParsedAddress(
+                final boolean isValid,
+                final String personalName,
+                final String localPart,
+                final String domain,
+                final InternetAddress internetAddress,
+                final boolean validReturnPath,
+                final String returnPathAddress) {
+
+            this.isValid = isValid;
+            this.personalName = personalName;
+            this.internetAddress = internetAddress;
+            this.domain = domain;
+            this.localPart = localPart;
+            this.validReturnPath = validReturnPath;
+            this.returnPathAddress = returnPathAddress;
+        }
+
+        /**
+         * Returns {@code true} if email is valid.
+         */
+        public boolean isValid() {
+            return isValid;
+        }
+
+        /**
+         * Returns personal name. Returned string does
+         * not reflect any decoding of RFC-2047 encoded personal names.
+         */
+        public String getPersonalName() {
+            return personalName;
+        }
+
+        /**
+         * Returns local part of the email address.
+         */
+        public String getLocalPart() {
+            return localPart;
+        }
+
+        /**
+         * Returns domain part of the email address.
+         */
+        public String getDomain() {
+            return domain;
+        }
+
+        /**
+         * Given a 2822-valid single address string, returns an InternetAddress object holding
+         * that address, otherwise returns null. The email address that comes back from the
+         * resulting InternetAddress object's getAddress() call will have comments and unnecessary
+         * quotation marks or whitespace removed.
+         */
+        public InternetAddress getInternetAddress() {
+            return internetAddress;
+        }
+
+        /**
+         * Returns {@code true} if the email represents a valid return path.
+         */
+        public boolean isValidReturnPath() {
+            return validReturnPath;
+        }
+
+        /**
+         * Pulls out the cleaned-up return path address. May return an empty string.
+         * Returns null if there are any syntax issues or other weirdness, otherwise
+         * the valid, trimmed return path email address without CFWS, surrounding angle brackets,
+         * with quotes stripped where possible, etc. (may return an empty string).
+         */
+        public String getReturnPathAddress() {
+            return returnPathAddress;
+        }
+    }
 
 }

+ 374 - 382
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ReceiveMailSession.java

@@ -25,13 +25,7 @@
 
 package com.fjhx.utils.mail;
 
-import jakarta.mail.FetchProfile;
-import jakarta.mail.Flags;
-import jakarta.mail.Folder;
-import jakarta.mail.Message;
-import jakarta.mail.MessagingException;
-import jakarta.mail.Session;
-import jakarta.mail.Store;
+import jakarta.mail.*;
 
 import java.io.File;
 import java.util.function.Consumer;
@@ -42,380 +36,378 @@ import java.util.function.Consumer;
  */
 public class ReceiveMailSession extends MailSession<Store> {
 
-	/**
-	 * Default folder.
-	 */
-	protected static final String DEFAULT_FOLDER = "INBOX";
-
-	/**
-	 * The current folder.
-	 */
-	Folder folder;
-	String folderName;
-
-	private final File attachmentStorage;
-
-	static {
-		setupSystemMailProperties();
-	}
-
-	/**
-	 * Creates new mail session.
-	 *
-	 * @param session {@link Session}.
-	 * @param store   {@link Store}.
-	 */
-	public ReceiveMailSession(final Session session, final Store store, final File attachmentStorage) {
-		super(session, store);
-		this.attachmentStorage = attachmentStorage;
-	}
-
-	@Override
-	public Store getService() {
-		return (Store) service;
-	}
-
-	// ---------------------------------------------------------------- folders
-
-	/**
-	 * Returns array of all {@link Folder}s as {@code String}s. You can use these names in
-	 * {@link #useFolder(String)} method.
-	 *
-	 * @return array of all {@link Folder}s as {@code String}s.
-	 */
-	public String[] getAllFolders() {
-		final Folder[] folders;
-		try {
-			folders = getService().getDefaultFolder().list("*");
-		} catch (final MessagingException msgexc) {
-			throw new MailException("Failed to connect to folder", msgexc);
-		}
-		final String[] folderNames = new String[folders.length];
-
-		for (int i = 0; i < folders.length; i++) {
-			final Folder folder = folders[i];
-			folderNames[i] = folder.getFullName();
-		}
-		return folderNames;
-	}
-
-	/**
-	 * Opens new folder and closes previously opened folder.
-	 *
-	 * @param folderName Folder to open
-	 */
-	public void useFolder(final String folderName) {
-		useFolder(folderName, Folder.READ_WRITE);
-	}
-
-	/**
-	 * Opens new folder and closes previously opened folder with a specific mode.
-	 *
-	 * @param folderName Folder to open
-	 * @param mode Mode to set
-	 */
-	public void useFolder(final String folderName, final int mode) {
-		closeFolderIfOpened(folder);
-
-		try {
-			this.folderName = folderName;
-			this.folder = getService().getFolder(folderName);
-			try {
-				folder.open(mode);
-			} catch (final MailException ignore) {
-				folder.open(Folder.READ_ONLY);
-			}
-		} catch (final MessagingException msgexc) {
-			throw new MailException("Failed to connect to folder: " + folderName, msgexc);
-		}
-	}
-
-	/**
-	 * Just returns a folder, w/o opening.
-	 */
-	public Folder getFolder(final String folder) {
-		try {
-			return getService().getFolder(folder);
-		} catch (final MessagingException e) {
-			throw new MailException("Folder not found: " + folder, e);
-		}
-	}
-
-	// ---------------------------------------------------------------- open
-
-	/**
-	 * Opens default folder: DEFAULT_FOLDER.
-	 */
-	public void useDefaultFolder() {
-		closeFolderIfOpened(folder);
-		useFolder(DEFAULT_FOLDER);
-	}
-
-	private void useAndOpenFolderIfNotSet() {
-		if (folder == null) {
-			if (folderName != null) {
-				useFolder(folderName);
-			}
-			else {
-				useDefaultFolder();
-			}
-		}
-	}
-
-	// ---------------------------------------------------------------- message count
-
-	/**
-	 * Returns number of messages.
-	 *
-	 * @return The number of messages.
-	 */
-	public int getMessageCount() {
-		useAndOpenFolderIfNotSet();
-		try {
-			return folder.getMessageCount();
-		} catch (final MessagingException msgexc) {
-			throw new MailException(msgexc);
-		}
-	}
-
-	/**
-	 * Returns the number of new messages.
-	 *
-	 * @return The number of new message.
-	 */
-	public int getNewMessageCount() {
-		useAndOpenFolderIfNotSet();
-		try {
-			return folder.getNewMessageCount();
-		} catch (final MessagingException msgexc) {
-			throw new MailException(msgexc);
-		}
-	}
-
-	/**
-	 * Returns the number of unread messages.
-	 */
-	public int getUnreadMessageCount() {
-		useAndOpenFolderIfNotSet();
-		try {
-			return folder.getUnreadMessageCount();
-		} catch (final MessagingException msgexc) {
-			throw new MailException(msgexc);
-		}
-	}
-
-	/**
-	 * Returns the number of deleted messages.
-	 *
-	 * @return The number of deleted messages.
-	 */
-	public int getDeletedMessageCount() {
-		useAndOpenFolderIfNotSet();
-		try {
-			return folder.getDeletedMessageCount();
-		} catch (final MessagingException msgexc) {
-			throw new MailException(msgexc);
-		}
-	}
-
-	// ---------------------------------------------------------------- receive builder
-
-	/**
-	 * Defines the process of received email in a fluent way.
-	 */
-	public ReceiverBuilder receive() {
-		return new ReceiverBuilder(this);
-	}
-
-	// ---------------------------------------------------------------- receive emails
-
-	/**
-	 * Receives all emails. Messages are not modified. However, servers
-	 * may set SEEN flag anyway, so we force messages to remain
-	 * unseen.
-	 *
-	 * @return array of {@link ReceivedEmail}s.
-	 */
-	public ReceivedEmail[] receiveEmail() {
-		return receiveMessages(null, null, null, false, null);
-	}
-
-	/**
-	 * Receives all emails that matches given {@link EmailFilter}.
-	 * Messages are not modified. However, servers may set SEEN flag anyway,
-	 * so we force messages to remain unseen.
-	 *
-	 * @param filter {@link EmailFilter}
-	 * @return array of {@link ReceivedEmail}s.
-	 */
-	public ReceivedEmail[] receiveEmail(final EmailFilter filter) {
-		return receiveMessages(filter, null, null, false, null);
-	}
-
-	/**
-	 * Receives all emails and mark all messages as 'seen' (ie 'read').
-	 *
-	 * @return array of {@link ReceivedEmail}s.
-	 * @see #receiveEmailAndMarkSeen(EmailFilter)
-	 */
-	public ReceivedEmail[] receiveEmailAndMarkSeen() {
-		return receiveEmailAndMarkSeen(null);
-	}
-
-	/**
-	 * Receives all emails that matches given {@link EmailFilter}
-	 * and mark them as 'seen' (ie 'read').
-	 *
-	 * @param filter {@link EmailFilter}
-	 * @return array of {@link ReceivedEmail}s.
-	 */
-	public ReceivedEmail[] receiveEmailAndMarkSeen(final EmailFilter filter) {
-		final Flags flagsToSet = new Flags();
-		flagsToSet.add(Flags.Flag.SEEN);
-		return receiveMessages(filter, flagsToSet, null, false, null);
-	}
-
-	/**
-	 * Receives all emails and mark all messages as 'seen' and 'deleted'.
-	 *
-	 * @return array of {@link ReceivedEmail}s.
-	 */
-	public ReceivedEmail[] receiveEmailAndDelete() {
-		return receiveEmailAndDelete(null);
-	}
-
-	/**
-	 * Receives all emails that matches given {@link EmailFilter} and
-	 * mark all messages as 'seen' and 'deleted'.
-	 *
-	 * @param filter {@link EmailFilter}
-	 * @return array of {@link ReceivedEmail}s.
-	 */
-	public ReceivedEmail[] receiveEmailAndDelete(final EmailFilter filter) {
-		final Flags flags = new Flags();
-		flags.add(Flags.Flag.SEEN);
-		flags.add(Flags.Flag.DELETED);
-		return receiveMessages(filter, flags, null, false, null);
-	}
-
-	public ReceivedEmail[] receiveEnvelopes() {
-		return receiveEnvelopes(null);
-	}
-
-	public ReceivedEmail[] receiveEnvelopes(final EmailFilter filter) {
-		return receiveMessages(filter, null, null, true, null);
-	}
-
-	/**
-	 * The main email receiving method.
-	 */
-	ReceivedEmail[] receiveMessages(
-			final EmailFilter filter,
-			final Flags flagsToSet,
-			final Flags flagsToUnset,
-			final boolean envelope,
-			final Consumer<Message[]> processedMessageConsumer) {
-		useAndOpenFolderIfNotSet();
-
-		final Message[] messages;
-
-		try {
-			if (filter == null) {
-				messages = folder.getMessages();
-			} else {
-				messages = folder.search(filter.getSearchTerm());
-			}
-
-			if (messages.length == 0) {
-				return ReceivedEmail.EMPTY_ARRAY;
-			}
-
-			if (envelope) {
-				final FetchProfile fetchProfile = new FetchProfile();
-
-				fetchProfile.add(FetchProfile.Item.ENVELOPE);
-				fetchProfile.add(FetchProfile.Item.FLAGS);
-
-				folder.fetch(messages, fetchProfile);
-			}
-
-			// process messages
-
-			final ReceivedEmail[] emails = new ReceivedEmail[messages.length];
-
-			for (int i = 0; i < messages.length; i++) {
-				final Message msg = messages[i];
-
-				// we need to parse message BEFORE flags are set!
-				emails[i] = new ReceivedEmail(msg, envelope, attachmentStorage);
-
-				if (!EmailUtil.isEmptyFlags(flagsToSet)) {
-					emails[i].flags(flagsToSet);
-					msg.setFlags(flagsToSet, true);
-				}
-
-				if (!EmailUtil.isEmptyFlags(flagsToUnset)) {
-					emails[i].flags().remove(flagsToUnset);
-					msg.setFlags(flagsToUnset, false);
-				}
-
-				if (EmailUtil.isEmptyFlags(flagsToSet) && !emails[i].isSeen()) {
-					msg.setFlag(Flags.Flag.SEEN, false);
-				}
-			}
-
-			if (processedMessageConsumer != null) {
-				processedMessageConsumer.accept(messages);
-			}
-
-			// if messages were marked to be deleted, we need to expunge the folder
-			if (!EmailUtil.isEmptyFlags(flagsToSet)) {
-				if (flagsToSet.contains(Flags.Flag.DELETED)) {
-					folder.expunge();
-				}
-			}
-
-			return emails;
-		} catch (final MessagingException msgexc) {
-			throw new MailException("Failed to fetch messages", msgexc);
-		}
-	}
-
-
-	// ---------------------------------------------------------------- update
-
-	/**
-	 * Updates the email flags on the server.
-	 */
-	public void updateEmailFlags(final ReceivedEmail receivedEmail) {
-		useAndOpenFolderIfNotSet();
-		try {
-			folder.setFlags(new int[] {receivedEmail.messageNumber()}, receivedEmail.flags(),true);
-		} catch (final MessagingException mex) {
-			throw new MailException("Failed to fetch messages", mex);
-		}
-	}
-
-	// ---------------------------------------------------------------- close
-
-	/**
-	 * Closes folder if opened and expunge deleted messages.
-	 */
-	protected static void closeFolderIfOpened(final Folder folder) {
-		if (folder != null && folder.isOpen()) {
-			try {
-				folder.close(true);
-			} catch (final MessagingException ignore) {
-			}
-		}
-	}
-
-	@Override
-	public void close() {
-		closeFolderIfOpened(folder);
-		folder = null;
-		folderName = null;
-		super.close();
-	}
+    /**
+     * Default folder.
+     */
+    protected static final String DEFAULT_FOLDER = "INBOX";
+
+    static {
+        setupSystemMailProperties();
+    }
+
+    private final File attachmentStorage;
+    /**
+     * The current folder.
+     */
+    Folder folder;
+    String folderName;
+
+    /**
+     * Creates new mail session.
+     *
+     * @param session {@link Session}.
+     * @param store   {@link Store}.
+     */
+    public ReceiveMailSession(final Session session, final Store store, final File attachmentStorage) {
+        super(session, store);
+        this.attachmentStorage = attachmentStorage;
+    }
+
+    /**
+     * Closes folder if opened and expunge deleted messages.
+     */
+    protected static void closeFolderIfOpened(final Folder folder) {
+        if (folder != null && folder.isOpen()) {
+            try {
+                folder.close(true);
+            } catch (final MessagingException ignore) {
+            }
+        }
+    }
+
+    // ---------------------------------------------------------------- folders
+
+    @Override
+    public Store getService() {
+        return (Store) service;
+    }
+
+    /**
+     * Returns array of all {@link Folder}s as {@code String}s. You can use these names in
+     * {@link #useFolder(String)} method.
+     *
+     * @return array of all {@link Folder}s as {@code String}s.
+     */
+    public String[] getAllFolders() {
+        final Folder[] folders;
+        try {
+            folders = getService().getDefaultFolder().list("*");
+        } catch (final MessagingException msgexc) {
+            throw new MailException("Failed to connect to folder", msgexc);
+        }
+        final String[] folderNames = new String[folders.length];
+
+        for (int i = 0; i < folders.length; i++) {
+            final Folder folder = folders[i];
+            folderNames[i] = folder.getFullName();
+        }
+        return folderNames;
+    }
+
+    /**
+     * Opens new folder and closes previously opened folder.
+     *
+     * @param folderName Folder to open
+     */
+    public void useFolder(final String folderName) {
+        useFolder(folderName, Folder.READ_WRITE);
+    }
+
+    /**
+     * Opens new folder and closes previously opened folder with a specific mode.
+     *
+     * @param folderName Folder to open
+     * @param mode       Mode to set
+     */
+    public void useFolder(final String folderName, final int mode) {
+        closeFolderIfOpened(folder);
+
+        try {
+            this.folderName = folderName;
+            this.folder = getService().getFolder(folderName);
+            try {
+                folder.open(mode);
+            } catch (final MailException ignore) {
+                folder.open(Folder.READ_ONLY);
+            }
+        } catch (final MessagingException msgexc) {
+            throw new MailException("Failed to connect to folder: " + folderName, msgexc);
+        }
+    }
+
+    // ---------------------------------------------------------------- open
+
+    /**
+     * Just returns a folder, w/o opening.
+     */
+    public Folder getFolder(final String folder) {
+        try {
+            return getService().getFolder(folder);
+        } catch (final MessagingException e) {
+            throw new MailException("Folder not found: " + folder, e);
+        }
+    }
+
+    /**
+     * Opens default folder: DEFAULT_FOLDER.
+     */
+    public void useDefaultFolder() {
+        closeFolderIfOpened(folder);
+        useFolder(DEFAULT_FOLDER);
+    }
+
+    // ---------------------------------------------------------------- message count
+
+    private void useAndOpenFolderIfNotSet() {
+        if (folder == null) {
+            if (folderName != null) {
+                useFolder(folderName);
+            } else {
+                useDefaultFolder();
+            }
+        }
+    }
+
+    /**
+     * Returns number of messages.
+     *
+     * @return The number of messages.
+     */
+    public int getMessageCount() {
+        useAndOpenFolderIfNotSet();
+        try {
+            return folder.getMessageCount();
+        } catch (final MessagingException msgexc) {
+            throw new MailException(msgexc);
+        }
+    }
+
+    /**
+     * Returns the number of new messages.
+     *
+     * @return The number of new message.
+     */
+    public int getNewMessageCount() {
+        useAndOpenFolderIfNotSet();
+        try {
+            return folder.getNewMessageCount();
+        } catch (final MessagingException msgexc) {
+            throw new MailException(msgexc);
+        }
+    }
+
+    /**
+     * Returns the number of unread messages.
+     */
+    public int getUnreadMessageCount() {
+        useAndOpenFolderIfNotSet();
+        try {
+            return folder.getUnreadMessageCount();
+        } catch (final MessagingException msgexc) {
+            throw new MailException(msgexc);
+        }
+    }
+
+    // ---------------------------------------------------------------- receive builder
+
+    /**
+     * Returns the number of deleted messages.
+     *
+     * @return The number of deleted messages.
+     */
+    public int getDeletedMessageCount() {
+        useAndOpenFolderIfNotSet();
+        try {
+            return folder.getDeletedMessageCount();
+        } catch (final MessagingException msgexc) {
+            throw new MailException(msgexc);
+        }
+    }
+
+    // ---------------------------------------------------------------- receive emails
+
+    /**
+     * Defines the process of received email in a fluent way.
+     */
+    public ReceiverBuilder receive() {
+        return new ReceiverBuilder(this);
+    }
+
+    /**
+     * Receives all emails. Messages are not modified. However, servers
+     * may set SEEN flag anyway, so we force messages to remain
+     * unseen.
+     *
+     * @return array of {@link ReceivedEmail}s.
+     */
+    public ReceivedEmail[] receiveEmail() {
+        return receiveMessages(null, null, null, false, null);
+    }
+
+    /**
+     * Receives all emails that matches given {@link EmailFilter}.
+     * Messages are not modified. However, servers may set SEEN flag anyway,
+     * so we force messages to remain unseen.
+     *
+     * @param filter {@link EmailFilter}
+     * @return array of {@link ReceivedEmail}s.
+     */
+    public ReceivedEmail[] receiveEmail(final EmailFilter filter) {
+        return receiveMessages(filter, null, null, false, null);
+    }
+
+    /**
+     * Receives all emails and mark all messages as 'seen' (ie 'read').
+     *
+     * @return array of {@link ReceivedEmail}s.
+     * @see #receiveEmailAndMarkSeen(EmailFilter)
+     */
+    public ReceivedEmail[] receiveEmailAndMarkSeen() {
+        return receiveEmailAndMarkSeen(null);
+    }
+
+    /**
+     * Receives all emails that matches given {@link EmailFilter}
+     * and mark them as 'seen' (ie 'read').
+     *
+     * @param filter {@link EmailFilter}
+     * @return array of {@link ReceivedEmail}s.
+     */
+    public ReceivedEmail[] receiveEmailAndMarkSeen(final EmailFilter filter) {
+        final Flags flagsToSet = new Flags();
+        flagsToSet.add(Flags.Flag.SEEN);
+        return receiveMessages(filter, flagsToSet, null, false, null);
+    }
+
+    /**
+     * Receives all emails and mark all messages as 'seen' and 'deleted'.
+     *
+     * @return array of {@link ReceivedEmail}s.
+     */
+    public ReceivedEmail[] receiveEmailAndDelete() {
+        return receiveEmailAndDelete(null);
+    }
+
+    /**
+     * Receives all emails that matches given {@link EmailFilter} and
+     * mark all messages as 'seen' and 'deleted'.
+     *
+     * @param filter {@link EmailFilter}
+     * @return array of {@link ReceivedEmail}s.
+     */
+    public ReceivedEmail[] receiveEmailAndDelete(final EmailFilter filter) {
+        final Flags flags = new Flags();
+        flags.add(Flags.Flag.SEEN);
+        flags.add(Flags.Flag.DELETED);
+        return receiveMessages(filter, flags, null, false, null);
+    }
+
+    public ReceivedEmail[] receiveEnvelopes() {
+        return receiveEnvelopes(null);
+    }
+
+    public ReceivedEmail[] receiveEnvelopes(final EmailFilter filter) {
+        return receiveMessages(filter, null, null, true, null);
+    }
+
+
+    // ---------------------------------------------------------------- update
+
+    /**
+     * The main email receiving method.
+     */
+    ReceivedEmail[] receiveMessages(
+            final EmailFilter filter,
+            final Flags flagsToSet,
+            final Flags flagsToUnset,
+            final boolean envelope,
+            final Consumer<Message[]> processedMessageConsumer) {
+        useAndOpenFolderIfNotSet();
+
+        final Message[] messages;
+
+        try {
+            if (filter == null) {
+                messages = folder.getMessages();
+            } else {
+                messages = folder.search(filter.getSearchTerm());
+            }
+
+            if (messages.length == 0) {
+                return ReceivedEmail.EMPTY_ARRAY;
+            }
+
+            if (envelope) {
+                final FetchProfile fetchProfile = new FetchProfile();
+
+                fetchProfile.add(FetchProfile.Item.ENVELOPE);
+                fetchProfile.add(FetchProfile.Item.FLAGS);
+
+                folder.fetch(messages, fetchProfile);
+            }
+
+            // process messages
+
+            final ReceivedEmail[] emails = new ReceivedEmail[messages.length];
+
+            for (int i = 0; i < messages.length; i++) {
+                final Message msg = messages[i];
+
+                // we need to parse message BEFORE flags are set!
+                emails[i] = new ReceivedEmail(msg, envelope, attachmentStorage);
+
+                if (!EmailUtil.isEmptyFlags(flagsToSet)) {
+                    emails[i].flags(flagsToSet);
+                    msg.setFlags(flagsToSet, true);
+                }
+
+                if (!EmailUtil.isEmptyFlags(flagsToUnset)) {
+                    emails[i].flags().remove(flagsToUnset);
+                    msg.setFlags(flagsToUnset, false);
+                }
+
+                if (EmailUtil.isEmptyFlags(flagsToSet) && !emails[i].isSeen()) {
+                    msg.setFlag(Flags.Flag.SEEN, false);
+                }
+            }
+
+            if (processedMessageConsumer != null) {
+                processedMessageConsumer.accept(messages);
+            }
+
+            // if messages were marked to be deleted, we need to expunge the folder
+            if (!EmailUtil.isEmptyFlags(flagsToSet)) {
+                if (flagsToSet.contains(Flags.Flag.DELETED)) {
+                    folder.expunge();
+                }
+            }
+
+            return emails;
+        } catch (final MessagingException msgexc) {
+            throw new MailException("Failed to fetch messages", msgexc);
+        }
+    }
+
+    // ---------------------------------------------------------------- close
+
+    /**
+     * Updates the email flags on the server.
+     */
+    public void updateEmailFlags(final ReceivedEmail receivedEmail) {
+        useAndOpenFolderIfNotSet();
+        try {
+            folder.setFlags(new int[]{receivedEmail.messageNumber()}, receivedEmail.flags(), true);
+        } catch (final MessagingException mex) {
+            throw new MailException("Failed to fetch messages", mex);
+        }
+    }
+
+    @Override
+    public void close() {
+        closeFolderIfOpened(folder);
+        folder = null;
+        folderName = null;
+        super.close();
+    }
 
 }

+ 96 - 102
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ReceivedEmail.java

@@ -47,7 +47,44 @@ import static jakarta.mail.Flags.Flag;
 public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
 
     public static final ReceivedEmail[] EMPTY_ARRAY = new ReceivedEmail[0];
+    /**
+     * {@link List} of attached {@link ReceivedEmail}s.
+     */
+    private final List<ReceivedEmail> attachedMessages = new ArrayList<>();
     private File attachmentStorage;
+    /**
+     * {@link Message} for this {@link ReceivedEmail}.
+     */
+    private Message originalMessage;
+    /**
+     * {@link Flags} for this {@link ReceivedEmail}.
+     */
+    private Flags flags;
+    private int messageNumber;
+    private String messageId;
+    private Date receivedDate;
+
+    /**
+     * Creates an empty {@link ReceivedEmail}.
+     */
+    private ReceivedEmail() {
+    }
+
+    /**
+     * Creates a {@link ReceivedEmail} from a given {@link Message}.
+     *
+     * @param msg      {@link Message}
+     * @param envelope flag if this is an envelope
+     */
+    public ReceivedEmail(final Message msg, final boolean envelope, final File attachmentStorage) {
+        this.attachmentStorage = attachmentStorage;
+        this.originalMessage = msg;
+        try {
+            parseMessage(msg, envelope);
+        } catch (final Exception ex) {
+            throw new MailException("Message parsing failed", ex);
+        }
+    }
 
     /**
      * Static constructor for fluent interface.
@@ -58,6 +95,59 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         return new ReceivedEmail();
     }
 
+    /**
+     * Returns the Content-ID of this {@link Part}. Returns {@code null} if none present.
+     *
+     * @param part {@link Part} the Part to parse.
+     * @return String containing content ID.
+     * @throws MessagingException if there is a failure.
+     * @see MimePart#getContentID()
+     */
+    protected static String parseContentId(final Part part) throws MessagingException {
+        if (part instanceof MimePart) {
+            final MimePart mp = (MimePart) part;
+            return mp.getContentID();
+        } else {
+            return null;
+        }
+    }
+
+    // ---------------------------------------------------------------- original message
+
+    /**
+     * Returns {@code true} if the {@link Part} is inline.
+     *
+     * @param part {@link Part} to parse.
+     * @return {@code true} if the {@link Part} is inline.
+     * @throws MessagingException if there is a failure.
+     */
+    protected static boolean parseInline(final Part part) throws MessagingException {
+        if (part instanceof MimePart) {
+            final String dispositionId = part.getDisposition();
+            return dispositionId != null && dispositionId.equalsIgnoreCase("inline");
+        }
+        return false;
+    }
+
+    /**
+     * Creates {@link EmailAttachmentBuilder} from {@link Part} and sets Content ID, inline and name.
+     *
+     * @param part {@link Part}.
+     * @return this
+     * @see #attachment(EmailAttachment)
+     */
+    private static EmailAttachmentBuilder addAttachmentInfo(final Part part) throws MessagingException {
+
+        final String fileName = EmailUtil.resolveFileName(part);
+        final String contentId = parseContentId(part);
+        final boolean isInline = parseInline(part);
+
+        return new EmailAttachmentBuilder()
+                .name(fileName)
+                .contentId(contentId)
+                .inline(isInline);
+    }
+
     @Override
     public ReceivedEmail clone() {
         return create()
@@ -95,27 +185,7 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
                 .attachedMessages(attachedMessages());
     }
 
-    /**
-     * Creates an empty {@link ReceivedEmail}.
-     */
-    private ReceivedEmail() {
-    }
-
-    /**
-     * Creates a {@link ReceivedEmail} from a given {@link Message}.
-     *
-     * @param msg      {@link Message}
-     * @param envelope flag if this is an envelope
-     */
-    public ReceivedEmail(final Message msg, final boolean envelope, final File attachmentStorage) {
-        this.attachmentStorage = attachmentStorage;
-        this.originalMessage = msg;
-        try {
-            parseMessage(msg, envelope);
-        } catch (final Exception ex) {
-            throw new MailException("Message parsing failed", ex);
-        }
-    }
+    // ---------------------------------------------------------------- flags
 
     /**
      * Parses {@link Message} and extracts all data for the received message.
@@ -167,7 +237,6 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         }
     }
 
-
     /**
      * Process part of the received message. All parts are simply added to the {@link ReceivedEmail},
      * i.e. hierarchy is not saved.
@@ -232,45 +301,6 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
     }
 
     /**
-     * Returns the Content-ID of this {@link Part}. Returns {@code null} if none present.
-     *
-     * @param part {@link Part} the Part to parse.
-     * @return String containing content ID.
-     * @throws MessagingException if there is a failure.
-     * @see MimePart#getContentID()
-     */
-    protected static String parseContentId(final Part part) throws MessagingException {
-        if (part instanceof MimePart) {
-            final MimePart mp = (MimePart) part;
-            return mp.getContentID();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Returns {@code true} if the {@link Part} is inline.
-     *
-     * @param part {@link Part} to parse.
-     * @return {@code true} if the {@link Part} is inline.
-     * @throws MessagingException if there is a failure.
-     */
-    protected static boolean parseInline(final Part part) throws MessagingException {
-        if (part instanceof MimePart) {
-            final String dispositionId = part.getDisposition();
-            return dispositionId != null && dispositionId.equalsIgnoreCase("inline");
-        }
-        return false;
-    }
-
-    // ---------------------------------------------------------------- original message
-
-    /**
-     * {@link Message} for this {@link ReceivedEmail}.
-     */
-    private Message originalMessage;
-
-    /**
      * @return {@link Message}
      */
     public Message originalMessage() {
@@ -287,13 +317,6 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         return this;
     }
 
-    // ---------------------------------------------------------------- flags
-
-    /**
-     * {@link Flags} for this {@link ReceivedEmail}.
-     */
-    private Flags flags;
-
     /**
      * @return {@link Flags}
      */
@@ -320,6 +343,8 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         return flags.contains(Flag.ANSWERED);
     }
 
+    // ---------------------------------------------------------------- additional properties
+
     /**
      * Returns {@code true} if message is deleted.
      *
@@ -363,11 +388,6 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         return flags.contains(Flag.SEEN);
     }
 
-    // ---------------------------------------------------------------- additional properties
-
-    private int messageNumber;
-    private String messageId;
-
     /**
      * Returns message number.
      *
@@ -403,7 +423,7 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         return this;
     }
 
-    private Date receivedDate;
+    // ---------------------------------------------------------------- attachments
 
     /**
      * Sets email's received {@link Date}.
@@ -425,8 +445,6 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         return receivedDate;
     }
 
-    // ---------------------------------------------------------------- attachments
-
     /**
      * Adds received attachment.
      *
@@ -445,6 +463,8 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
         return storeAttachment(builder.buildByteArrayDataSource());
     }
 
+    // ---------------------------------------------------------------- inner messages
+
     /**
      * Adds received attachment.
      *
@@ -462,32 +482,6 @@ public class ReceivedEmail extends CommonEmail<ReceivedEmail> {
     }
 
     /**
-     * Creates {@link EmailAttachmentBuilder} from {@link Part} and sets Content ID, inline and name.
-     *
-     * @param part {@link Part}.
-     * @return this
-     * @see #attachment(EmailAttachment)
-     */
-    private static EmailAttachmentBuilder addAttachmentInfo(final Part part) throws MessagingException {
-
-        final String fileName = EmailUtil.resolveFileName(part);
-        final String contentId = parseContentId(part);
-        final boolean isInline = parseInline(part);
-
-        return new EmailAttachmentBuilder()
-                .name(fileName)
-                .contentId(contentId)
-                .inline(isInline);
-    }
-
-    // ---------------------------------------------------------------- inner messages
-
-    /**
-     * {@link List} of attached {@link ReceivedEmail}s.
-     */
-    private final List<ReceivedEmail> attachedMessages = new ArrayList<>();
-
-    /**
      * Adds attached {@link ReceivedEmail}s.
      *
      * @param emails {@link List} of {@link ReceivedEmail}s to attach.

+ 96 - 95
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/ReceiverBuilder.java

@@ -33,100 +33,101 @@ import jakarta.mail.MessagingException;
  */
 public class ReceiverBuilder {
 
-	private final ReceiveMailSession session;
-	private EmailFilter filter;
-	private final Flags flagsToSet = new Flags();
-	private final Flags flagsToUnset = new Flags();
-	private boolean envelopeOnly;
-	private String targetFolder;
-	private String fromFolder;
-
-	public ReceiverBuilder(final ReceiveMailSession session) {
-		this.session = session;
-	}
-
-	/**
-	 * Define applied filters.
-	 */
-	public ReceiverBuilder filter(final EmailFilter emailFilter) {
-		this.filter = emailFilter;
-		return this;
-	}
-
-	/**
-	 * Marks messages as seen after receiving them.
-	 */
-	public ReceiverBuilder markSeen() {
-		this.flagsToSet.add(Flags.Flag.SEEN);
-		return this;
-	}
-
-	/**
-	 * Marks message with given flag.
-	 */
-	public ReceiverBuilder mark(final Flags.Flag flagToSet) {
-		this.flagsToSet.add(flagToSet);
-		return this;
-	}
-
-	/**
-	 * Unmarks a message with given flag.
-	 */
-	public ReceiverBuilder unmark(final Flags.Flag flagToUnset) {
-		this.flagsToUnset.add(flagToUnset);
-		return this;
-	}
-
-	/**
-	 * Deletes messages upon receiving.
-	 */
-	public ReceiverBuilder markDeleted() {
-		this.flagsToSet.add(Flags.Flag.DELETED);
-		return this;
-	}
-
-	/**
-	 * Sets the working folder.
-\	 */
-	public ReceiverBuilder fromFolder(final String fromFolder) {
-		this.fromFolder = fromFolder;
-		return this;
-	}
-
-	/**
-	 * Defines target folder where message will be moved.
-	 */
-	public ReceiverBuilder moveToFolder(final String targetFolder) {
-		this.markDeleted();
-		this.targetFolder = targetFolder;
-		return this;
-	}
-
-	/**
-	 * Receives only envelopes.
-	 */
-	public ReceiverBuilder envelopeOnly() {
-		this.envelopeOnly = true;
-		return this;
-	}
-
-	/**
-	 * Receives the emails as specified by the builder.
-	 */
-	public ReceivedEmail[] get() {
-		if (fromFolder != null) {
-			session.useFolder(fromFolder);
-		}
-
-		return session.receiveMessages(filter, flagsToSet, flagsToUnset, envelopeOnly, messages -> {
-			if (targetFolder != null) {
-				try {
-					session.folder.copyMessages(messages, session.getFolder(targetFolder));
-				} catch (MessagingException e) {
-					throw new MailException("Copying messages failed");
-				}
-			}
-		});
-	}
+    private final ReceiveMailSession session;
+    private final Flags flagsToSet = new Flags();
+    private final Flags flagsToUnset = new Flags();
+    private EmailFilter filter;
+    private boolean envelopeOnly;
+    private String targetFolder;
+    private String fromFolder;
+
+    public ReceiverBuilder(final ReceiveMailSession session) {
+        this.session = session;
+    }
+
+    /**
+     * Define applied filters.
+     */
+    public ReceiverBuilder filter(final EmailFilter emailFilter) {
+        this.filter = emailFilter;
+        return this;
+    }
+
+    /**
+     * Marks messages as seen after receiving them.
+     */
+    public ReceiverBuilder markSeen() {
+        this.flagsToSet.add(Flags.Flag.SEEN);
+        return this;
+    }
+
+    /**
+     * Marks message with given flag.
+     */
+    public ReceiverBuilder mark(final Flags.Flag flagToSet) {
+        this.flagsToSet.add(flagToSet);
+        return this;
+    }
+
+    /**
+     * Unmarks a message with given flag.
+     */
+    public ReceiverBuilder unmark(final Flags.Flag flagToUnset) {
+        this.flagsToUnset.add(flagToUnset);
+        return this;
+    }
+
+    /**
+     * Deletes messages upon receiving.
+     */
+    public ReceiverBuilder markDeleted() {
+        this.flagsToSet.add(Flags.Flag.DELETED);
+        return this;
+    }
+
+    /**
+     * Sets the working folder.
+     * \
+     */
+    public ReceiverBuilder fromFolder(final String fromFolder) {
+        this.fromFolder = fromFolder;
+        return this;
+    }
+
+    /**
+     * Defines target folder where message will be moved.
+     */
+    public ReceiverBuilder moveToFolder(final String targetFolder) {
+        this.markDeleted();
+        this.targetFolder = targetFolder;
+        return this;
+    }
+
+    /**
+     * Receives only envelopes.
+     */
+    public ReceiverBuilder envelopeOnly() {
+        this.envelopeOnly = true;
+        return this;
+    }
+
+    /**
+     * Receives the emails as specified by the builder.
+     */
+    public ReceivedEmail[] get() {
+        if (fromFolder != null) {
+            session.useFolder(fromFolder);
+        }
+
+        return session.receiveMessages(filter, flagsToSet, flagsToUnset, envelopeOnly, messages -> {
+            if (targetFolder != null) {
+                try {
+                    session.folder.copyMessages(messages, session.getFolder(targetFolder));
+                } catch (MessagingException e) {
+                    throw new MailException("Copying messages failed");
+                }
+            }
+        });
+    }
 
 }

+ 312 - 316
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SendMailSession.java

@@ -37,11 +37,7 @@ import jakarta.mail.internet.MimeMessage;
 import jakarta.mail.internet.MimeMultipart;
 import jodd.util.StringPool;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static jakarta.mail.Message.RecipientType;
 
@@ -50,316 +46,316 @@ import static jakarta.mail.Message.RecipientType;
  */
 public class SendMailSession extends MailSession<Transport> {
 
-	private static final String ALTERNATIVE = "alternative";
-	private static final String RELATED = "related";
-	private static final String CHARSET = ";charset=";
-	private static final String INLINE = "inline";
-
-	static {
-		setupSystemMailProperties();
-	}
-
-	/**
-	 * Creates new mail session.
-	 *
-	 * @param session   {@link Session}
-	 * @param transport {@link Transport}
-	 */
-	public SendMailSession(final Session session, final Transport transport) {
-		super(session, transport);
-	}
-
-	@Override
-	public Transport getService() {
-		return (Transport) service;
-	}
-
-	/**
-	 * Prepares message and sends it. Returns Message ID of sent email.
-	 *
-	 * @param email {@link Email} to send.
-	 * @return String representing message ID.
-	 */
-	public String sendMail(final Email email) {
-		try {
-			final MimeMessage msg = createMessage(email);
-			getService().sendMessage(msg, msg.getAllRecipients());
-			return msg.getMessageID();
-		} catch (final MessagingException msgexc) {
-			throw new MailException("Failed to send email: " + email, msgexc);
-		}
-	}
-
-	// ---------------------------------------------------------------- adapter
-
-	/**
-	 * Creates new {@link MimeMessage} from an {@link Email}.
-	 *
-	 * @param email {@link Email} to be created as a {@link MimeMessage}.
-	 * @return {@link MimeMessage} created from an {@link Email}.
-	 * @throws MessagingException if there is a failure
-	 */
-	protected MimeMessage createMessage(final Email email) throws MessagingException {
-		final Email clone = email.clone();
-
-		final MimeMessage newMsg = new MimeMessage(getSession());
-
-		setPeople(clone, newMsg);
-		setSubject(clone, newMsg);
-		setSentDate(clone, newMsg);
-		setHeaders(clone, newMsg);
-		addBodyData(clone, newMsg);
-		return newMsg;
-	}
-
-	/**
-	 * Sets subject in msgToSet from subject in emailWithData.
-	 *
-	 * @param emailWithData {@link Email} with data
-	 * @param msgToSet      {@link MimeMessage} to set data into.
-	 * @throws MessagingException if there is a failure
-	 */
-	private static void setSubject(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
-		if (emailWithData.subjectEncoding() != null) {
-			msgToSet.setSubject(emailWithData.subject(), emailWithData.subjectEncoding());
-		} else {
-			msgToSet.setSubject(emailWithData.subject());
-		}
-	}
-
-	/**
-	 * Sets sent date in msgToSet with sent date from emailWithData.
-	 *
-	 * @param emailWithData {@link Email} with data
-	 * @param msgToSet      {@link MimeMessage} to set data into.
-	 * @throws MessagingException if there is a failure
-	 */
-	private static void setSentDate(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
-		Date date = emailWithData.sentDate();
-		if (date == null) {
-			date = new Date();
-		}
-		msgToSet.setSentDate(date);
-	}
-
-	/**
-	 * Sets headers in msgToSet with headers from emailWithData.
-	 *
-	 * @param emailWithData {@link Email} with data
-	 * @param msgToSet      {@link MimeMessage} to set data into.
-	 * @throws MessagingException if there is a failure
-	 */
-	private static void setHeaders(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
-		final Map<String, String> headers = emailWithData.headers();
-		if (headers != null) {
-			for (final Map.Entry<String, String> entry : headers.entrySet()) {
-				msgToSet.setHeader(entry.getKey(), entry.getValue());
-			}
-		}
-	}
-
-	/**
-	 * Sets FROM, REPLY-TO and recipients.
-	 *
-	 * @param emailWithData {@link Email} with data
-	 * @param msgToSet      {@link MimeMessage} to set data into.
-	 * @throws MessagingException if there is a failure
-	 */
-	private void setPeople(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
-		msgToSet.setFrom(emailWithData.from().toInternetAddress());
-		msgToSet.setReplyTo(EmailAddress.convert(emailWithData.replyTo()));
-		setRecipients(emailWithData, msgToSet);
-	}
-
-	/**
-	 * Sets TO, CC and BCC in msgToSet with TO, CC and BCC from emailWithData.
-	 *
-	 * @param emailWithData {@link Email} with data
-	 * @param msgToSet      {@link MimeMessage} to set data into.
-	 * @throws MessagingException if there is a failure.
-	 */
-	private void setRecipients(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
-		// TO
-		final InternetAddress[] to = EmailAddress.convert(emailWithData.to());
-		if (to.length > 0) {
-			msgToSet.setRecipients(RecipientType.TO, to);
-		}
-
-		// CC
-		final InternetAddress[] cc = EmailAddress.convert(emailWithData.cc());
-		if (cc.length > 0) {
-			msgToSet.setRecipients(RecipientType.CC, cc);
-		}
-
-		// BCC
-		final InternetAddress[] bcc = EmailAddress.convert(emailWithData.bcc());
-		if (bcc.length > 0) {
-			msgToSet.setRecipients(RecipientType.BCC, bcc);
-		}
-	}
-
-	/**
-	 * Adds message data and attachments.
-	 *
-	 * @param emailWithData {@link Email} with data
-	 * @param msgToSet      {@link MimeMessage} to set data into.
-	 * @throws MessagingException if there is a failure.
-	 */
-	private void addBodyData(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
-		final List<EmailMessage> messages = emailWithData.messages();
-
-		final int totalMessages = messages.size();
-
-		// Need to use new list since filterEmbeddedAttachments(List) removes attachments from the source List
-		final List<EmailAttachment<? extends DataSource>> attachments = new ArrayList<>(emailWithData.attachments());
-
-		if (attachments.isEmpty() && totalMessages == 1) {
-			// special case: no attachments and just one content
-			setContent(messages.get(0), msgToSet);
-
-		} else {
-			final MimeMultipart multipart = new MimeMultipart();
-
-			final MimeMultipart msgMultipart = new MimeMultipart(ALTERNATIVE);
-			multipart.addBodyPart(getBaseBodyPart(msgMultipart));
-			for (final EmailMessage emailMessage : messages) {
-				msgMultipart.addBodyPart(getBodyPart(emailMessage, attachments));
-			}
-
-			addAnyAttachments(attachments, multipart);
-
-			msgToSet.setContent(multipart);
-		}
-	}
-
-	/**
-	 * Returns new {@link MimeBodyPart} with content set as msgMultipart.
-	 *
-	 * @param msgMultipart {@link MimeMultipart} to add to the new {@link MimeBodyPart}.
-	 * @return new {@link MimeBodyPart} with content set as msgMultipart.
-	 * @throws MessagingException if there is a failure.
-	 */
-	private static MimeBodyPart getBaseBodyPart(final MimeMultipart msgMultipart) throws MessagingException {
-		final MimeBodyPart bodyPart = new MimeBodyPart();
-		bodyPart.setContent(msgMultipart);
-		return bodyPart;
-	}
-
-	/**
-	 * @param emailMessage {@link EmailMessage} with data.
-	 * @param attachments  {@link List} of {@link EmailAttachment}s.
-	 * @return new {@link MimeBodyPart} with data from emailMessage and attachments.
-	 * @throws MessagingException if there is a failure.
-	 */
-	private MimeBodyPart getBodyPart(final EmailMessage emailMessage, final List<EmailAttachment<? extends DataSource>> attachments) throws MessagingException {
-
-		final MimeBodyPart bodyPart = new MimeBodyPart();
-
-		// detect embedded attachments
-		final List<EmailAttachment<? extends DataSource>> embeddedAttachments = filterEmbeddedAttachments(attachments, emailMessage);
-
-		if (embeddedAttachments.isEmpty()) {
-			// no embedded attachments, just add message
-			setContent(emailMessage, bodyPart);
-		} else {
-			attachments.removeAll(embeddedAttachments);
-
-			// embedded attachments detected, join them as related
-			final MimeMultipart relatedMultipart = new MimeMultipart(RELATED);
-
-			final MimeBodyPart messageData = new MimeBodyPart();
-
-			setContent(emailMessage, messageData);
-
-			relatedMultipart.addBodyPart(messageData);
-
-			addAnyAttachments(embeddedAttachments, relatedMultipart);
-
-			bodyPart.setContent(relatedMultipart);
-		}
-
-		return bodyPart;
-	}
-
-	/**
-	 * Sets emailWithData content into msgToSet.
-	 *
-	 * @param emailWithData {@link EmailMessage} with data.
-	 * @param partToSet     {@link Part} to set data into.
-	 * @throws MessagingException if there is a failure.
-	 */
-	private static void setContent(final EmailMessage emailWithData, final Part partToSet) throws MessagingException {
-		partToSet.setContent(emailWithData.getContent(), emailWithData.getMimeType() + CHARSET + emailWithData.getEncoding());
-	}
-
-	/**
-	 * Creates attachment body part. Handles regular and inline attachments.
-	 *
-	 * @param attachment Body part {@link EmailAttachment}.
-	 * @return {@link MimeBodyPart} which represents body part attachment.
-	 * @throws MessagingException if there is a failure.
-	 */
-	protected MimeBodyPart createAttachmentBodyPart(final EmailAttachment<? extends DataSource> attachment) throws MessagingException {
-		final MimeBodyPart part = new MimeBodyPart();
-
-		final String attachmentName = attachment.getEncodedName();
-		if (attachmentName != null) {
-			part.setFileName(attachmentName);
-		}
-
-		part.setDataHandler(new DataHandler(attachment.getDataSource()));
-
-		if (attachment.getContentId() != null) {
-			part.setContentID(StringPool.LEFT_CHEV + attachment.getContentId() + StringPool.RIGHT_CHEV);
-		}
-		if (attachment.isInline()) {
-			part.setDisposition(INLINE);
-		}
-
-		return part;
-	}
-
-	/**
-	 * Filters out the {@link List} of embedded {@link EmailAttachment}s for given {@link EmailMessage}.
-	 * This will remove the embedded attachments from the {@link List} and return them in a new {@link List}.
-	 *
-	 * @param attachments  {@link List} of attachments to search for in emailMessage.
-	 * @param emailMessage {@link EmailMessage} to see if attachment is embedded into.
-	 * @return {@link List} of embedded {@link EmailAttachment}s; otherwise, returns empty {@link List}.
-	 */
-	protected static List<EmailAttachment<? extends DataSource>> filterEmbeddedAttachments(final List<EmailAttachment<? extends DataSource>> attachments, final EmailMessage emailMessage) {
-		final List<EmailAttachment<? extends DataSource>> embeddedAttachments = new ArrayList<>();
-
-		if (attachments == null || attachments.isEmpty() || emailMessage == null) {
-			return embeddedAttachments;
-		}
-
-		final Iterator<EmailAttachment<? extends DataSource>> iterator = attachments.iterator();
-
-		while (iterator.hasNext()) {
-			final EmailAttachment<? extends DataSource> emailAttachment = iterator.next();
-
-			if (emailAttachment.isEmbeddedInto(emailMessage)) {
-				embeddedAttachments.add(emailAttachment);
-				iterator.remove();
-			}
-		}
-
-		return embeddedAttachments;
-	}
-
-	/**
-	 * Adds {@link List} of {@link EmailAttachment}s to multipart.
-	 *
-	 * @param attachments {@link List} of {@link EmailAttachment}s to add to multipart. This can be {@code null}.
-	 * @param multipart   {@link MimeMultipart} to set data into.
-	 * @throws MessagingException if there is a failure.
-	 */
-	private void addAnyAttachments(final List<EmailAttachment<? extends DataSource>> attachments, final MimeMultipart multipart) throws MessagingException {
-		for (final EmailAttachment<? extends DataSource> attachment : attachments) {
-			final MimeBodyPart bodyPart = createAttachmentBodyPart(attachment);
-			multipart.addBodyPart(bodyPart);
-		}
-	}
+    private static final String ALTERNATIVE = "alternative";
+    private static final String RELATED = "related";
+    private static final String CHARSET = ";charset=";
+    private static final String INLINE = "inline";
+
+    static {
+        setupSystemMailProperties();
+    }
+
+    /**
+     * Creates new mail session.
+     *
+     * @param session   {@link Session}
+     * @param transport {@link Transport}
+     */
+    public SendMailSession(final Session session, final Transport transport) {
+        super(session, transport);
+    }
+
+    /**
+     * Sets subject in msgToSet from subject in emailWithData.
+     *
+     * @param emailWithData {@link Email} with data
+     * @param msgToSet      {@link MimeMessage} to set data into.
+     * @throws MessagingException if there is a failure
+     */
+    private static void setSubject(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
+        if (emailWithData.subjectEncoding() != null) {
+            msgToSet.setSubject(emailWithData.subject(), emailWithData.subjectEncoding());
+        } else {
+            msgToSet.setSubject(emailWithData.subject());
+        }
+    }
+
+    /**
+     * Sets sent date in msgToSet with sent date from emailWithData.
+     *
+     * @param emailWithData {@link Email} with data
+     * @param msgToSet      {@link MimeMessage} to set data into.
+     * @throws MessagingException if there is a failure
+     */
+    private static void setSentDate(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
+        Date date = emailWithData.sentDate();
+        if (date == null) {
+            date = new Date();
+        }
+        msgToSet.setSentDate(date);
+    }
+
+    // ---------------------------------------------------------------- adapter
+
+    /**
+     * Sets headers in msgToSet with headers from emailWithData.
+     *
+     * @param emailWithData {@link Email} with data
+     * @param msgToSet      {@link MimeMessage} to set data into.
+     * @throws MessagingException if there is a failure
+     */
+    private static void setHeaders(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
+        final Map<String, String> headers = emailWithData.headers();
+        if (headers != null) {
+            for (final Map.Entry<String, String> entry : headers.entrySet()) {
+                msgToSet.setHeader(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+
+    /**
+     * Returns new {@link MimeBodyPart} with content set as msgMultipart.
+     *
+     * @param msgMultipart {@link MimeMultipart} to add to the new {@link MimeBodyPart}.
+     * @return new {@link MimeBodyPart} with content set as msgMultipart.
+     * @throws MessagingException if there is a failure.
+     */
+    private static MimeBodyPart getBaseBodyPart(final MimeMultipart msgMultipart) throws MessagingException {
+        final MimeBodyPart bodyPart = new MimeBodyPart();
+        bodyPart.setContent(msgMultipart);
+        return bodyPart;
+    }
+
+    /**
+     * Sets emailWithData content into msgToSet.
+     *
+     * @param emailWithData {@link EmailMessage} with data.
+     * @param partToSet     {@link Part} to set data into.
+     * @throws MessagingException if there is a failure.
+     */
+    private static void setContent(final EmailMessage emailWithData, final Part partToSet) throws MessagingException {
+        partToSet.setContent(emailWithData.getContent(), emailWithData.getMimeType() + CHARSET + emailWithData.getEncoding());
+    }
+
+    /**
+     * Filters out the {@link List} of embedded {@link EmailAttachment}s for given {@link EmailMessage}.
+     * This will remove the embedded attachments from the {@link List} and return them in a new {@link List}.
+     *
+     * @param attachments  {@link List} of attachments to search for in emailMessage.
+     * @param emailMessage {@link EmailMessage} to see if attachment is embedded into.
+     * @return {@link List} of embedded {@link EmailAttachment}s; otherwise, returns empty {@link List}.
+     */
+    protected static List<EmailAttachment<? extends DataSource>> filterEmbeddedAttachments(final List<EmailAttachment<? extends DataSource>> attachments, final EmailMessage emailMessage) {
+        final List<EmailAttachment<? extends DataSource>> embeddedAttachments = new ArrayList<>();
+
+        if (attachments == null || attachments.isEmpty() || emailMessage == null) {
+            return embeddedAttachments;
+        }
+
+        final Iterator<EmailAttachment<? extends DataSource>> iterator = attachments.iterator();
+
+        while (iterator.hasNext()) {
+            final EmailAttachment<? extends DataSource> emailAttachment = iterator.next();
+
+            if (emailAttachment.isEmbeddedInto(emailMessage)) {
+                embeddedAttachments.add(emailAttachment);
+                iterator.remove();
+            }
+        }
+
+        return embeddedAttachments;
+    }
+
+    @Override
+    public Transport getService() {
+        return (Transport) service;
+    }
+
+    /**
+     * Prepares message and sends it. Returns Message ID of sent email.
+     *
+     * @param email {@link Email} to send.
+     * @return String representing message ID.
+     */
+    public String sendMail(final Email email) {
+        try {
+            final MimeMessage msg = createMessage(email);
+            getService().sendMessage(msg, msg.getAllRecipients());
+            return msg.getMessageID();
+        } catch (final MessagingException msgexc) {
+            throw new MailException("Failed to send email: " + email, msgexc);
+        }
+    }
+
+    /**
+     * Creates new {@link MimeMessage} from an {@link Email}.
+     *
+     * @param email {@link Email} to be created as a {@link MimeMessage}.
+     * @return {@link MimeMessage} created from an {@link Email}.
+     * @throws MessagingException if there is a failure
+     */
+    protected MimeMessage createMessage(final Email email) throws MessagingException {
+        final Email clone = email.clone();
+
+        final MimeMessage newMsg = new MimeMessage(getSession());
+
+        setPeople(clone, newMsg);
+        setSubject(clone, newMsg);
+        setSentDate(clone, newMsg);
+        setHeaders(clone, newMsg);
+        addBodyData(clone, newMsg);
+        return newMsg;
+    }
+
+    /**
+     * Sets FROM, REPLY-TO and recipients.
+     *
+     * @param emailWithData {@link Email} with data
+     * @param msgToSet      {@link MimeMessage} to set data into.
+     * @throws MessagingException if there is a failure
+     */
+    private void setPeople(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
+        msgToSet.setFrom(emailWithData.from().toInternetAddress());
+        msgToSet.setReplyTo(EmailAddress.convert(emailWithData.replyTo()));
+        setRecipients(emailWithData, msgToSet);
+    }
+
+    /**
+     * Sets TO, CC and BCC in msgToSet with TO, CC and BCC from emailWithData.
+     *
+     * @param emailWithData {@link Email} with data
+     * @param msgToSet      {@link MimeMessage} to set data into.
+     * @throws MessagingException if there is a failure.
+     */
+    private void setRecipients(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
+        // TO
+        final InternetAddress[] to = EmailAddress.convert(emailWithData.to());
+        if (to.length > 0) {
+            msgToSet.setRecipients(RecipientType.TO, to);
+        }
+
+        // CC
+        final InternetAddress[] cc = EmailAddress.convert(emailWithData.cc());
+        if (cc.length > 0) {
+            msgToSet.setRecipients(RecipientType.CC, cc);
+        }
+
+        // BCC
+        final InternetAddress[] bcc = EmailAddress.convert(emailWithData.bcc());
+        if (bcc.length > 0) {
+            msgToSet.setRecipients(RecipientType.BCC, bcc);
+        }
+    }
+
+    /**
+     * Adds message data and attachments.
+     *
+     * @param emailWithData {@link Email} with data
+     * @param msgToSet      {@link MimeMessage} to set data into.
+     * @throws MessagingException if there is a failure.
+     */
+    private void addBodyData(final Email emailWithData, final MimeMessage msgToSet) throws MessagingException {
+        final List<EmailMessage> messages = emailWithData.messages();
+
+        final int totalMessages = messages.size();
+
+        // Need to use new list since filterEmbeddedAttachments(List) removes attachments from the source List
+        final List<EmailAttachment<? extends DataSource>> attachments = new ArrayList<>(emailWithData.attachments());
+
+        if (attachments.isEmpty() && totalMessages == 1) {
+            // special case: no attachments and just one content
+            setContent(messages.get(0), msgToSet);
+
+        } else {
+            final MimeMultipart multipart = new MimeMultipart();
+
+            final MimeMultipart msgMultipart = new MimeMultipart(ALTERNATIVE);
+            multipart.addBodyPart(getBaseBodyPart(msgMultipart));
+            for (final EmailMessage emailMessage : messages) {
+                msgMultipart.addBodyPart(getBodyPart(emailMessage, attachments));
+            }
+
+            addAnyAttachments(attachments, multipart);
+
+            msgToSet.setContent(multipart);
+        }
+    }
+
+    /**
+     * @param emailMessage {@link EmailMessage} with data.
+     * @param attachments  {@link List} of {@link EmailAttachment}s.
+     * @return new {@link MimeBodyPart} with data from emailMessage and attachments.
+     * @throws MessagingException if there is a failure.
+     */
+    private MimeBodyPart getBodyPart(final EmailMessage emailMessage, final List<EmailAttachment<? extends DataSource>> attachments) throws MessagingException {
+
+        final MimeBodyPart bodyPart = new MimeBodyPart();
+
+        // detect embedded attachments
+        final List<EmailAttachment<? extends DataSource>> embeddedAttachments = filterEmbeddedAttachments(attachments, emailMessage);
+
+        if (embeddedAttachments.isEmpty()) {
+            // no embedded attachments, just add message
+            setContent(emailMessage, bodyPart);
+        } else {
+            attachments.removeAll(embeddedAttachments);
+
+            // embedded attachments detected, join them as related
+            final MimeMultipart relatedMultipart = new MimeMultipart(RELATED);
+
+            final MimeBodyPart messageData = new MimeBodyPart();
+
+            setContent(emailMessage, messageData);
+
+            relatedMultipart.addBodyPart(messageData);
+
+            addAnyAttachments(embeddedAttachments, relatedMultipart);
+
+            bodyPart.setContent(relatedMultipart);
+        }
+
+        return bodyPart;
+    }
+
+    /**
+     * Creates attachment body part. Handles regular and inline attachments.
+     *
+     * @param attachment Body part {@link EmailAttachment}.
+     * @return {@link MimeBodyPart} which represents body part attachment.
+     * @throws MessagingException if there is a failure.
+     */
+    protected MimeBodyPart createAttachmentBodyPart(final EmailAttachment<? extends DataSource> attachment) throws MessagingException {
+        final MimeBodyPart part = new MimeBodyPart();
+
+        final String attachmentName = attachment.getEncodedName();
+        if (attachmentName != null) {
+            part.setFileName(attachmentName);
+        }
+
+        part.setDataHandler(new DataHandler(attachment.getDataSource()));
+
+        if (attachment.getContentId() != null) {
+            part.setContentID(StringPool.LEFT_CHEV + attachment.getContentId() + StringPool.RIGHT_CHEV);
+        }
+        if (attachment.isInline()) {
+            part.setDisposition(INLINE);
+        }
+
+        return part;
+    }
+
+    /**
+     * Adds {@link List} of {@link EmailAttachment}s to multipart.
+     *
+     * @param attachments {@link List} of {@link EmailAttachment}s to add to multipart. This can be {@code null}.
+     * @param multipart   {@link MimeMultipart} to set data into.
+     * @throws MessagingException if there is a failure.
+     */
+    private void addAnyAttachments(final List<EmailAttachment<? extends DataSource>> attachments, final MimeMultipart multipart) throws MessagingException {
+        for (final EmailAttachment<? extends DataSource> attachment : attachments) {
+            final MimeBodyPart bodyPart = createAttachmentBodyPart(attachment);
+            multipart.addBodyPart(bodyPart);
+        }
+    }
 
 }

+ 21 - 21
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SimpleAuthenticator.java

@@ -33,28 +33,28 @@ import jakarta.mail.PasswordAuthentication;
  */
 public class SimpleAuthenticator extends Authenticator {
 
-	private final String username;
-	private final String password;
+    private final String username;
+    private final String password;
 
-	/**
-	 * Creates new SimpleAuthenticator for given username and password.
-	 *
-	 * @param username Username
-	 * @param password Password
-	 */
-	public SimpleAuthenticator(final String username, final String password) {
-		this.username = username;
-		this.password = password;
-	}
+    /**
+     * Creates new SimpleAuthenticator for given username and password.
+     *
+     * @param username Username
+     * @param password Password
+     */
+    public SimpleAuthenticator(final String username, final String password) {
+        this.username = username;
+        this.password = password;
+    }
 
-	/**
-	 * Return new {@link PasswordAuthentication} for given username and password.
-	 *
-	 * @return {@link PasswordAuthentication}
-	 */
-	@Override
-	public PasswordAuthentication getPasswordAuthentication() {
-		return new PasswordAuthentication(username, password);
-	}
+    /**
+     * Return new {@link PasswordAuthentication} for given username and password.
+     *
+     * @return {@link PasswordAuthentication}
+     */
+    @Override
+    public PasswordAuthentication getPasswordAuthentication() {
+        return new PasswordAuthentication(username, password);
+    }
 }
 

+ 69 - 68
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SmtpServer.java

@@ -38,73 +38,74 @@ import static jodd.util.StringPool.TRUE;
  */
 public class SmtpServer extends MailServer<SendMailSession> {
 
-	protected static final String PROTOCOL_SMTP = "smtp";
-
-	/**
-	 * Default SMTP port
-	 */
-	protected static final int DEFAULT_SMTP_PORT = 25;
-
-	// ---------------------------------------------------------------- create
-
-	public SmtpServer(final Builder builder) {
-		super(builder, DEFAULT_SMTP_PORT);
-	}
-	protected SmtpServer(final Builder builder, final int defaultPort) {
-		super(builder, defaultPort);
-	}
-
-	// ---------------------------------------------------------------- properties
-
-	@Override
-	protected Properties createSessionProperties() {
-		final Properties props = super.createSessionProperties();
-
-		props.setProperty(MAIL_TRANSPORT_PROTOCOL, PROTOCOL_SMTP);
-		props.setProperty(MAIL_HOST, host);
-		props.setProperty(MAIL_SMTP_HOST, host);
-		props.setProperty(MAIL_SMTP_PORT, String.valueOf(port));
-
-		if (authenticator != null) {
-			props.setProperty(MAIL_SMTP_AUTH, TRUE);
-		}
-
-		if (timeout > 0) {
-			final String timeoutValue = String.valueOf(timeout);
-			props.put(MAIL_SMTP_CONNECTIONTIMEOUT, timeoutValue);
-			props.put(MAIL_SMTP_TIMEOUT, timeoutValue);
-			props.put(MAIL_SMTP_WRITETIMEOUT, timeoutValue);
-		}
-
-		return props;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 *
-	 * @return {@link SendMailSession}
-	 */
-	@Override
-	public SendMailSession createSession() {
-		final Session session = Session.getInstance(createSessionProperties(), authenticator);
-		final Transport mailTransport;
-		try {
-			mailTransport = getTransport(session);
-		} catch (final NoSuchProviderException nspex) {
-			throw new MailException(nspex);
-		}
-		return new SendMailSession(session, mailTransport);
-	}
-
-	/**
-	 * Get the {@link Transport} for {@link Session}.
-	 *
-	 * @param session The {@link SendMailSession}.
-	 * @return SMTP {@link Transport}.
-	 * @throws NoSuchProviderException If provider for the given protocol is not found.
-	 */
-	protected Transport getTransport(final Session session) throws NoSuchProviderException {
-		return session.getTransport(PROTOCOL_SMTP);
-	}
+    protected static final String PROTOCOL_SMTP = "smtp";
+
+    /**
+     * Default SMTP port
+     */
+    protected static final int DEFAULT_SMTP_PORT = 25;
+
+    // ---------------------------------------------------------------- create
+
+    public SmtpServer(final Builder builder) {
+        super(builder, DEFAULT_SMTP_PORT);
+    }
+
+    protected SmtpServer(final Builder builder, final int defaultPort) {
+        super(builder, defaultPort);
+    }
+
+    // ---------------------------------------------------------------- properties
+
+    @Override
+    protected Properties createSessionProperties() {
+        final Properties props = super.createSessionProperties();
+
+        props.setProperty(MAIL_TRANSPORT_PROTOCOL, PROTOCOL_SMTP);
+        props.setProperty(MAIL_HOST, host);
+        props.setProperty(MAIL_SMTP_HOST, host);
+        props.setProperty(MAIL_SMTP_PORT, String.valueOf(port));
+
+        if (authenticator != null) {
+            props.setProperty(MAIL_SMTP_AUTH, TRUE);
+        }
+
+        if (timeout > 0) {
+            final String timeoutValue = String.valueOf(timeout);
+            props.put(MAIL_SMTP_CONNECTIONTIMEOUT, timeoutValue);
+            props.put(MAIL_SMTP_TIMEOUT, timeoutValue);
+            props.put(MAIL_SMTP_WRITETIMEOUT, timeoutValue);
+        }
+
+        return props;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@link SendMailSession}
+     */
+    @Override
+    public SendMailSession createSession() {
+        final Session session = Session.getInstance(createSessionProperties(), authenticator);
+        final Transport mailTransport;
+        try {
+            mailTransport = getTransport(session);
+        } catch (final NoSuchProviderException nspex) {
+            throw new MailException(nspex);
+        }
+        return new SendMailSession(session, mailTransport);
+    }
+
+    /**
+     * Get the {@link Transport} for {@link Session}.
+     *
+     * @param session The {@link SendMailSession}.
+     * @return SMTP {@link Transport}.
+     * @throws NoSuchProviderException If provider for the given protocol is not found.
+     */
+    protected Transport getTransport(final Session session) throws NoSuchProviderException {
+        return session.getTransport(PROTOCOL_SMTP);
+    }
 
 }

+ 67 - 69
bladex-saas-project/new-mail/src/main/java/com/fjhx/utils/mail/SmtpSslServer.java

@@ -34,74 +34,72 @@ import java.util.Properties;
  */
 public class SmtpSslServer extends SmtpServer {
 
-	/**
-	 * Default SMTP SSL port.
-	 */
-	protected static final int DEFAULT_SSL_PORT = 465;
-
-	public SmtpSslServer(final Builder builder) {
-		super(builder, DEFAULT_SSL_PORT);
-	}
-
-	// ---------------------------------------------------------------- properties
-
-	/**
-	 * Defaults to {@code false}.
-	 */
-	protected boolean startTlsRequired = false;
-
-	/**
-	 * Defaults to {@code false}. Google requires it to be false
-	 */
-	protected boolean plaintextOverTLS = false;
-
-	/**
-	 * Sets <code>mail.smtp.starttls.required</code>.
-	 * <p>
-	 * If the server doesn't support the STARTTLS command, or the command fails,
-	 * the connect method will fail. Defaults to {@code false}.
-	 *
-	 * @param startTlsRequired If {@code true}, requires the use of the STARTTLS command.
-	 * @return this
-	 */
-	public SmtpSslServer startTlsRequired(final boolean startTlsRequired) {
-		this.startTlsRequired = startTlsRequired;
-		return this;
-	}
-
-	/**
-	 * When enabled, <code>MAIL_SMTP_SOCKET_FACTORY_CLASS</code> will be not set,
-	 * and Plaintext Authentication over TLS will be enabled.
-	 *
-	 * @param plaintextOverTLS {@code true} when plain text authentication over TLS should be enabled.
-	 * @return this
-	 */
-	public SmtpSslServer plaintextOverTLS(final boolean plaintextOverTLS) {
-		this.plaintextOverTLS = plaintextOverTLS;
-		return this;
-	}
-
-	@Override
-	protected Properties createSessionProperties() {
-		final Properties props = super.createSessionProperties();
-
-		props.setProperty(MAIL_SMTP_STARTTLS_REQUIRED,
-			startTlsRequired ? StringPool.TRUE : StringPool.FALSE);
-
-		props.setProperty(MAIL_SMTP_STARTTLS_ENABLE, StringPool.TRUE);
-
-		props.setProperty(MAIL_SMTP_SOCKET_FACTORY_PORT, String.valueOf(port));
-
-		props.setProperty(MAIL_SMTP_PORT, String.valueOf(port));
-
-		if (!plaintextOverTLS) {
-			props.setProperty(MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
-		}
-
-		props.setProperty(MAIL_SMTP_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);
-		props.setProperty(MAIL_HOST, host);
-
-		return props;
-	}
+    /**
+     * Default SMTP SSL port.
+     */
+    protected static final int DEFAULT_SSL_PORT = 465;
+    /**
+     * Defaults to {@code false}.
+     */
+    protected boolean startTlsRequired = false;
+
+    // ---------------------------------------------------------------- properties
+    /**
+     * Defaults to {@code false}. Google requires it to be false
+     */
+    protected boolean plaintextOverTLS = false;
+
+    public SmtpSslServer(final Builder builder) {
+        super(builder, DEFAULT_SSL_PORT);
+    }
+
+    /**
+     * Sets <code>mail.smtp.starttls.required</code>.
+     * <p>
+     * If the server doesn't support the STARTTLS command, or the command fails,
+     * the connect method will fail. Defaults to {@code false}.
+     *
+     * @param startTlsRequired If {@code true}, requires the use of the STARTTLS command.
+     * @return this
+     */
+    public SmtpSslServer startTlsRequired(final boolean startTlsRequired) {
+        this.startTlsRequired = startTlsRequired;
+        return this;
+    }
+
+    /**
+     * When enabled, <code>MAIL_SMTP_SOCKET_FACTORY_CLASS</code> will be not set,
+     * and Plaintext Authentication over TLS will be enabled.
+     *
+     * @param plaintextOverTLS {@code true} when plain text authentication over TLS should be enabled.
+     * @return this
+     */
+    public SmtpSslServer plaintextOverTLS(final boolean plaintextOverTLS) {
+        this.plaintextOverTLS = plaintextOverTLS;
+        return this;
+    }
+
+    @Override
+    protected Properties createSessionProperties() {
+        final Properties props = super.createSessionProperties();
+
+        props.setProperty(MAIL_SMTP_STARTTLS_REQUIRED,
+                startTlsRequired ? StringPool.TRUE : StringPool.FALSE);
+
+        props.setProperty(MAIL_SMTP_STARTTLS_ENABLE, StringPool.TRUE);
+
+        props.setProperty(MAIL_SMTP_SOCKET_FACTORY_PORT, String.valueOf(port));
+
+        props.setProperty(MAIL_SMTP_PORT, String.valueOf(port));
+
+        if (!plaintextOverTLS) {
+            props.setProperty(MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
+        }
+
+        props.setProperty(MAIL_SMTP_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);
+        props.setProperty(MAIL_HOST, host);
+
+        return props;
+    }
 
 }