From 32110bbcc164de4dfa5052dcb9f4d09190e26828 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Fri, 7 Feb 2025 14:21:06 +0100 Subject: [PATCH 1/3] Prefix example subject with example name --- .../com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java b/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java index 25d7da9..6751248 100644 --- a/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java +++ b/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java @@ -28,7 +28,7 @@ private void sendEmail(Address[] to, Address[] from, String exampleName) throws String[] parts = exampleName.split("-"); String builderType = parts[0]; boolean htmlLast = false; - String subject = SimpleEmail.getSubject(); + String subject = String.format("[%s] %s", exampleName, SimpleEmail.getSubject()); String textBody = null; String htmlBody = null; FileDataSource attachment = null; From 4060af3dcccda23a60993005fbd66339cf632a12 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Tue, 18 Feb 2025 15:38:54 +0100 Subject: [PATCH 2/3] Add Content-Disposition header to JSON-LD part --- .../extension/mime/MimeMultipartBuilder.java | 16 ++- .../MultipartAlternativeMessageBuilder.java | 10 +- .../mime/MultipartRelatedMessageBuilder.java | 4 +- .../sender/StructuredMimeParseUtils.java | 50 +++++++- .../extension/MailProcessingAdvancedTest.java | 23 ++-- .../eml/alternative-text-html-json-inline.eml | 108 ++++++++++++++++++ .../eml/alternative-text-json-html-inline.eml | 8 ++ 7 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 test/resources/eml/alternative-text-html-json-inline.eml create mode 100644 test/resources/eml/alternative-text-json-html-inline.eml diff --git a/src/com/audriga/jakarta/sml/extension/mime/MimeMultipartBuilder.java b/src/com/audriga/jakarta/sml/extension/mime/MimeMultipartBuilder.java index b3cfdf8..5f126f6 100644 --- a/src/com/audriga/jakarta/sml/extension/mime/MimeMultipartBuilder.java +++ b/src/com/audriga/jakarta/sml/extension/mime/MimeMultipartBuilder.java @@ -97,13 +97,27 @@ public MimeMultipartBuilder addBodyPartJsonLd(StructuredData structuredData) thr return addBodyPartJsonLd(structuredData, "utf-8"); } - public MimeMultipartBuilder addBodyPartJsonLd(StructuredData structuredData, String jsonLdEncoding) throws MessagingException { + public MimeMultipartBuilder addBodyPartJsonLd( + StructuredData structuredData, + String jsonLdEncoding + ) throws MessagingException { + return addBodyPartJsonLd(structuredData, jsonLdEncoding, null); + } + + public MimeMultipartBuilder addBodyPartJsonLd( + StructuredData structuredData, + String jsonLdEncoding, + String disposition + ) throws MessagingException { if (structuredData == null) { return this; } MimeBodyPart bodyPart = new MimeBodyPart(); String bodyPartType = StructuredData.MIME_TYPE + "; charset=" + jsonLdEncoding; bodyPart.setContent(structuredData, bodyPartType); + if (disposition != null) { + bodyPart.setDisposition(disposition); + } m.addBodyPart(bodyPart); return this; } diff --git a/src/com/audriga/jakarta/sml/extension/mime/MultipartAlternativeMessageBuilder.java b/src/com/audriga/jakarta/sml/extension/mime/MultipartAlternativeMessageBuilder.java index 077ff0d..749b3de 100644 --- a/src/com/audriga/jakarta/sml/extension/mime/MultipartAlternativeMessageBuilder.java +++ b/src/com/audriga/jakarta/sml/extension/mime/MultipartAlternativeMessageBuilder.java @@ -8,6 +8,7 @@ public class MultipartAlternativeMessageBuilder extends AbstractMessageBuilder { private MimeTextContent textBody; private boolean htmlLast = true; + private String disposition; public MultipartAlternativeMessageBuilder textBody(String textBody) { if (textBody != null) { @@ -21,6 +22,11 @@ public MultipartAlternativeMessageBuilder htmlLast(boolean htmlLast) { return this; } + public MultipartAlternativeMessageBuilder disposition(String disposition) { + this.disposition = disposition; + return this; + } + @Override protected MultipartAlternativeMessageBuilder self() { return this; @@ -42,12 +48,12 @@ public StructuredMimeMessageWrapper build() throws MessagingException { MimeMultipartBuilder multipartBuilder = new MimeMultipartBuilder(MimeMultipartBuilder.MULTIPART.ALTERNATIVE); if (htmlLast) { multipartBuilder.addBodyPartText(textBody); - multipartBuilder.addBodyPartJsonLd(structuredDataPart); + multipartBuilder.addBodyPartJsonLd(structuredDataPart, "utf-8", disposition); multipartBuilder.addBodyPartHtml(htmlBody); } else { multipartBuilder.addBodyPartText(textBody); multipartBuilder.addBodyPartHtml(htmlBody); - multipartBuilder.addBodyPartJsonLd(structuredDataPart); + multipartBuilder.addBodyPartJsonLd(structuredDataPart, "utf-8", disposition); } sm.resetContent(multipartBuilder.build()); diff --git a/src/com/audriga/jakarta/sml/extension/mime/MultipartRelatedMessageBuilder.java b/src/com/audriga/jakarta/sml/extension/mime/MultipartRelatedMessageBuilder.java index 05c3a44..1958bde 100644 --- a/src/com/audriga/jakarta/sml/extension/mime/MultipartRelatedMessageBuilder.java +++ b/src/com/audriga/jakarta/sml/extension/mime/MultipartRelatedMessageBuilder.java @@ -4,6 +4,7 @@ import com.audriga.jakarta.sml.h2lj.model.StructuredData; import jakarta.mail.Message; import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeBodyPart; import jakarta.mail.internet.MimeMultipart; public class MultipartRelatedMessageBuilder extends AbstractMessageBuilder { @@ -41,7 +42,8 @@ public StructuredMimeMessageWrapper build() throws MessagingException { MimeMultipart mm = new MimeMultipartBuilder(MimeMultipartBuilder.MULTIPART.RELATED) .addBodyPart(alternative) - .addBodyPartJsonLd(structuredDataPart, "utf-8") + // TODO Actually reference json-ld in related-text-html-json example via CID or similar + .addBodyPartJsonLd(structuredDataPart, "utf-8", MimeBodyPart.INLINE) .build(); sm.resetContent(mm); diff --git a/src/com/audriga/jakarta/sml/extension/sender/StructuredMimeParseUtils.java b/src/com/audriga/jakarta/sml/extension/sender/StructuredMimeParseUtils.java index 04a2f16..faf2252 100644 --- a/src/com/audriga/jakarta/sml/extension/sender/StructuredMimeParseUtils.java +++ b/src/com/audriga/jakarta/sml/extension/sender/StructuredMimeParseUtils.java @@ -4,6 +4,7 @@ import com.audriga.jakarta.sml.extension.model.MimeTextContent; import jakarta.mail.BodyPart; import jakarta.mail.MessagingException; +import jakarta.mail.Part; import jakarta.mail.internet.ContentType; import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMultipart; @@ -24,19 +25,39 @@ public static StructuredMimeMessageWrapper parseMessage(MimeMessage message) thr MimeTextContent htmlContent = parseBody(message, Collections.singletonList(TEXT_HTML)); smw.setHtmlBody(htmlContent); smw.setTextBody(parseBody(message, Arrays.asList(TEXT, TEXT_PLAIN, TEXT_ASCII))); + // TODO also add structured data here return smw; } + public static Part parsePart(MimeMessage message, List mimeTypes) throws MessagingException, IOException { + if (mimeTypes.stream().anyMatch(mimeType -> { + try { + return message.isMimeType(mimeType); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + })) { + return message; + } + if (message.isMimeType("multipart/*")) { + return getPartFromMultipart((MimeMultipart) message.getContent(), mimeTypes); + } + return null; + } + public static MimeTextContent parseBody(MimeMessage message, List mimeTypes) throws MessagingException, IOException { - for (String mimeType : mimeTypes) { - if (message.isMimeType(mimeType)) { - return new MimeTextContent((String) message.getContent(), message.getEncoding()); + if (mimeTypes.stream().anyMatch(mimeType -> { + try { + return message.isMimeType(mimeType); + } catch (MessagingException e) { + throw new RuntimeException(e); } + })) { + return new MimeTextContent((String) message.getContent(), message.getEncoding()); } if (message.isMimeType("multipart/*")) { - MimeMultipart mimeMultipart = (MimeMultipart) message.getContent(); - return getBodyFromMultipart(mimeMultipart, mimeTypes); + return getBodyFromMultipart((MimeMultipart) message.getContent(), mimeTypes); } return null; } @@ -62,4 +83,23 @@ private static MimeTextContent getBodyFromMultipart(MimeMultipart mimeMultipart, } return null; } + + private static Part getPartFromMultipart(MimeMultipart mimeMultipart, List mimeTypes) throws MessagingException, IOException { + for (int i = 0; i < mimeMultipart.getCount(); i++) { + BodyPart bodyPart = mimeMultipart.getBodyPart(i); + for (String mimeType : mimeTypes) { + if (bodyPart.isMimeType(mimeType)) { + return bodyPart; + } + } + if (bodyPart.isMimeType("multipart/*")) { + MimeMultipart nestedMultipart = (MimeMultipart) bodyPart.getContent(); + Part body = getPartFromMultipart(nestedMultipart, mimeTypes); + if (body != null) { + return body; + } + } + } + return null; + } } diff --git a/test/com/audriga/jakarta/sml/extension/MailProcessingAdvancedTest.java b/test/com/audriga/jakarta/sml/extension/MailProcessingAdvancedTest.java index af3df49..1d1451b 100644 --- a/test/com/audriga/jakarta/sml/extension/MailProcessingAdvancedTest.java +++ b/test/com/audriga/jakarta/sml/extension/MailProcessingAdvancedTest.java @@ -2,16 +2,19 @@ import com.audriga.jakarta.sml.TestUtils; import com.audriga.jakarta.sml.extension.mime.*; +import com.audriga.jakarta.sml.extension.sender.StructuredMimeParseUtils; import com.audriga.jakarta.sml.h2lj.model.StructuredData; import com.audriga.jakarta.sml.data.MultipartRelatedEmail; import com.audriga.jakarta.sml.data.SimpleEmail; import jakarta.mail.MessagingException; +import jakarta.mail.Part; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.IOException; import java.io.PrintStream; +import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,12 +31,14 @@ public void setUp() { @DataProvider(name = "emailVariantsAlternative") public Object[][] emailVariantsAlternative() { return new Object[][] { - { "eml/alternative-text-html-json.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), false }, - { "eml/alternative-html-json.eml", SimpleEmail.getSubject(), null, SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), false }, - { "eml/alternative-html.eml", SimpleEmail.getSubject(), null, SimpleEmail.getHtmlBody(), null, false }, - { "eml/alternative-text-json.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), null, SimpleEmail.getJson(), false }, - { "eml/alternative-text-html.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), null, false }, - { "eml/alternative-text-json-html.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), true }, + { "eml/alternative-text-html-json-inline.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), false, Part.INLINE}, + { "eml/alternative-text-html-json.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), false, null }, + { "eml/alternative-html-json.eml", SimpleEmail.getSubject(), null, SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), false, null }, + { "eml/alternative-html.eml", SimpleEmail.getSubject(), null, SimpleEmail.getHtmlBody(), null, false, null }, + { "eml/alternative-text-json.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), null, SimpleEmail.getJson(), false, null }, + { "eml/alternative-text-html.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), null, false, null }, + { "eml/alternative-text-json-html.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), true, null }, + {"eml/alternative-text-html-json-inline.eml", SimpleEmail.getSubject(), SimpleEmail.getTextBody(), SimpleEmail.getHtmlBody(), SimpleEmail.getJson(), true, Part.INLINE }, }; } @@ -57,12 +62,13 @@ public Object[][] emailVariantsHtml() { } @Test(dataProvider = "emailVariantsAlternative", groups = "unit") - public void testFullMultipartAlternativeGenerator(String emlFilePath, String subject, String textBody, String htmlBody, List jsonList, boolean htmlLast) throws MessagingException, IOException { + public void testFullMultipartAlternativeGenerator(String emlFilePath, String subject, String textBody, String htmlBody, List jsonList, boolean htmlLast, String disposition) throws MessagingException, IOException { // Parse StructuredMimeMessageWrapper result = TestUtils.parseEmlFile(emlFilePath); // Generate StructuredMimeMessageWrapper message = new MultipartAlternativeMessageBuilder() + .disposition(disposition) .subject(subject) .textBody(textBody) .htmlBody(htmlBody) @@ -110,6 +116,9 @@ public void testFullMultipartRelatedGenerator(String emlFilePath, String subject StructuredData generatedJson = message.getStructuredData().get(0); StructuredData resultJson = result.getStructuredData().get(0); assertEquals(generatedJson.getJson().toString(), resultJson.getJson().toString(), "Structured data of generated message should be equal to the parsed message"); + Part bodyPart = StructuredMimeParseUtils.parsePart(message.getMimeMessage(), Collections.singletonList(StructuredData.MIME_TYPE)); + assert bodyPart != null; + assertEquals(bodyPart.getDisposition(), Part.INLINE); } if (htmlBody != null) { assertEquals(message.getHtmlBody().getText(), result.getHtmlBody().getText(), "HTML of generated message should be equal to the parsed message"); diff --git a/test/resources/eml/alternative-text-html-json-inline.eml b/test/resources/eml/alternative-text-html-json-inline.eml new file mode 100644 index 0000000..bcc1ae4 --- /dev/null +++ b/test/resources/eml/alternative-text-html-json-inline.eml @@ -0,0 +1,108 @@ +Date: Wed, 19 Feb 2025 16:54:38 +0100 (CET) +Message-ID: <948424584.34.1739980478937@sadatoni> +Subject: Make Email Better Again! +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_33_1273879638.1739980478937" + +------=_Part_33_1273879638.1739980478937 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 7bit + +Event Reservation Confirmation + +Dear Noah Baumbach, + +Thank you for your reservation. Here are the details: + +Reservation Number: MBE12345 +Event Name: Make Better Email 2024 +Start Date: 2024-10-30 +Location: + Isode Ltd + 14 Castle Mews + Hampton TW12 2NP + UK + +We look forward to seeing you at the event! + +Best regards, +The Event Team +------=_Part_33_1273879638.1739980478937 +Content-Type: application/ld+json; charset=utf-8 +Content-Transfer-Encoding: 7bit +Content-Disposition: inline + +{ + "@context": "http://schema.org", + "@type": "EventReservation", + "reservationId": "MBE12345", + "underName": { + "@type": "Person", + "name": "Noah Baumbach" + }, + "reservationFor": { + "@type": "Event", + "name": "Make Better Email 2024", + "startDate": "2024-10-15", + "organizer": { + "@type": "Organization", + "name": "Fastmail Pty Ltd.", + "logo": "https://www.fastmail.com/assets/images/FM-Logo-RGB-IiFj8alCx1-3073.webp" + }, + "location": { + "@type": "Place", + "name": "Isode Ltd", + "address": { + "@type": "PostalAddress", + "streetAddress": "14 Castle Mews", + "addressLocality": "Hampton", + "addressRegion": "Greater London", + "postalCode": "TW12 2NP", + "addressCountry": "UK" + } + } + } +} +------=_Part_33_1273879638.1739980478937 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: 7bit + + + + + + Event Reservation + + +

Event Reservation Confirmation

+

Dear Noah Baumbach,

+

Thank you for your reservation. Here are the details:

+ + + + + + + + + + + + + + + + + +
Reservation Number:MBE12345
Event Name:Make Better Email 2024
Start Date:2024-10-30
Location: + Isode Ltd
+ 14 Castle Mews
+ Hampton TW12 2NP
+ UK +
+

We look forward to seeing you at the event!

+

Best regards,
The Event Team

+ + +------=_Part_33_1273879638.1739980478937-- \ No newline at end of file diff --git a/test/resources/eml/alternative-text-json-html-inline.eml b/test/resources/eml/alternative-text-json-html-inline.eml new file mode 100644 index 0000000..0f6f25b --- /dev/null +++ b/test/resources/eml/alternative-text-json-html-inline.eml @@ -0,0 +1,8 @@ +Date: Wed, 19 Feb 2025 16:49:57 +0100 (CET) +Message-ID: <948424584.34.1739980197900@sadatoni> +Subject: Make Email Better Again! +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_33_1273879638.1739980197899" + +------=_Part_33_1273879638.1739980197899 \ No newline at end of file From 7b375294578248cfe574c13041cf95ae3e315170 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Wed, 19 Feb 2025 17:10:35 +0100 Subject: [PATCH 3/3] Support configuring content disposition Only for multpart/alternative for now --- .../sml/extension/sender/EmailSenderIT.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java b/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java index 6751248..681e7aa 100644 --- a/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java +++ b/test/com/audriga/jakarta/sml/extension/sender/EmailSenderIT.java @@ -7,6 +7,7 @@ import jakarta.activation.FileDataSource; import jakarta.mail.Address; import jakarta.mail.MessagingException; +import jakarta.mail.Part; import jakarta.mail.internet.InternetAddress; import org.testng.annotations.Test; @@ -16,6 +17,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -23,6 +25,10 @@ public class EmailSenderIT { EmailSender sender; private static final Logger mLogger = Logger.getLogger(EmailSenderIT.class.getName()); + private static final String INLINE_BUILDER = "inline"; + private static final String HTML_BUILDER = "html"; + private static final String ALT_BUILDER = "alternative"; + private static final String RELATED_BUILDER = "related"; private void sendEmail(Address[] to, Address[] from, String exampleName) throws MessagingException, URISyntaxException { String[] parts = exampleName.split("-"); @@ -34,6 +40,7 @@ private void sendEmail(Address[] to, Address[] from, String exampleName) throws FileDataSource attachment = null; String attachmentName = null; List structuredDataList = new ArrayList<>(); + String disposition = null; for (int i = 1; i < parts.length; i++) { switch (parts[i]) { @@ -53,6 +60,12 @@ private void sendEmail(Address[] to, Address[] from, String exampleName) throws attachment = SimpleEmail.getAttachment(); attachmentName = SimpleEmail.getAttachmentName(); break; + case "inline": + disposition = Part.INLINE; + if (!Objects.equals(builderType, ALT_BUILDER)) { + mLogger.log(Level.WARNING, "For now inline disposition is only supported for multipart alternative Messages."); + } + break; default: throw new IllegalArgumentException( String.format("Unknown body part '%s' in example name '%s' taken from email subject.", @@ -67,7 +80,7 @@ private void sendEmail(Address[] to, Address[] from, String exampleName) throws StructuredMimeMessageWrapper message; switch (builderType) { - case "inline": + case INLINE_BUILDER: message = new InlineHtmlMessageBuilder() .subject(subject) .textBody(textBody) @@ -79,7 +92,7 @@ private void sendEmail(Address[] to, Address[] from, String exampleName) throws .addAttachment(attachment, attachmentName) .build(); break; - case "html": + case HTML_BUILDER: message = new HtmlOnlyMessageBuilder() .subject(subject) .htmlBody(htmlBody) @@ -88,8 +101,9 @@ private void sendEmail(Address[] to, Address[] from, String exampleName) throws .from(from) .build(); break; - case "alternative": + case ALT_BUILDER: message = new MultipartAlternativeMessageBuilder() + .disposition(disposition) .subject(subject) .textBody(textBody) .htmlBody(htmlBody) @@ -99,7 +113,7 @@ private void sendEmail(Address[] to, Address[] from, String exampleName) throws .from(from) .build(); break; - case "related": + case RELATED_BUILDER: message = new MultipartRelatedMessageBuilder() .subject(subject) .textBody(textBody)