From 8d58795264db31b0fbe562564ecc3aa4799ee84f Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 17 Dec 2024 13:46:03 +0100 Subject: [PATCH] Send SentMail Instances When Sending Emails This commit introduces the use of CDI events to emit instances of SentMail whenever an email is successfully sent. The SentMail class is an immutable representation of the sent email, preventing unintended modifications. By leveraging CDI events, this approach avoids the need to introduce a new API. Fixes https://github.com/quarkusio/quarkus/issues/45135. --- .../java/io/quarkus/mailer/InjectionTest.java | 46 +++++++++++++++++-- .../main/java/io/quarkus/mailer/SentMail.java | 27 +++++++++++ .../mailer/runtime/MutinyMailerImpl.java | 33 ++++++++++++- 3 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 extensions/mailer/runtime/src/main/java/io/quarkus/mailer/SentMail.java diff --git a/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java index 5073b2022a8e2b..4fe49df2ccb2cc 100644 --- a/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java +++ b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java @@ -1,10 +1,12 @@ package io.quarkus.mailer; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -28,7 +30,7 @@ public class InjectionTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(BeanUsingBareMailClient.class, BeanUsingBlockingMailer.class, - BeanUsingReactiveMailer.class, MailTemplates.class) + BeanUsingReactiveMailer.class, MailTemplates.class, MailListener.class) .addAsResource("mock-config.properties", "application.properties") .addAsResource(new StringAsset("" + "{name}"), "templates/test1.html") @@ -56,15 +58,33 @@ public class InjectionTest { @Inject MailTemplates templates; + @Inject + MailListener listener; + @Test public void testInjection() { beanUsingMutiny.verify(); beanUsingBare.verify(); beanUsingBlockingMailer.verify(); + + await().until(() -> listener.getLast() != null); + listener.reset(); + beanUsingReactiveMailer.verify().toCompletableFuture().join(); - templates.send1(); - templates.send2().await(); - templates.sendNative().await(); + await().until(() -> listener.getLast() != null); + listener.reset(); + + templates.send1().await().indefinitely(); + await().until(() -> listener.getLast() != null); + listener.reset(); + + templates.send2().await().indefinitely(); + await().until(() -> listener.getLast() != null); + listener.reset(); + + templates.sendNative().await().indefinitely(); + await().until(() -> listener.getLast() != null); + listener.reset(); assertEquals("Me", MailTemplates.Templates.testNative("Me").templateInstance().render()); } @@ -139,4 +159,22 @@ Uni sendNative() { return Templates.testNative("John").to("quarkus@quarkus.io").subject("Test").send(); } } + + @ApplicationScoped + public static class MailListener { + + volatile SentMail last; + + public void onMailSent(@Observes SentMail mail) { + last = mail; + } + + public SentMail getLast() { + return last; + } + + public void reset() { + last = null; + } + } } diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/SentMail.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/SentMail.java new file mode 100644 index 00000000000000..250048e0dcb8da --- /dev/null +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/SentMail.java @@ -0,0 +1,27 @@ +package io.quarkus.mailer; + +import java.util.List; +import java.util.Map; + +/** + * Represents a sent mail. + * Instances of this class are sent using CDI events. + * + * @param from the sender address + * @param to the list of recipients + * @param cc the list of CC recipients + * @param bcc the list of BCC recipients + * @param replyTo the list of reply-to addresses + * @param bounceAddress the bounce address + * @param subject the subject + * @param textBody the text body + * @param htmlBody the HTML body + * @param headers the headers + * @param attachments the attachments + */ +public record SentMail(String from, + List to, List cc, List bcc, + String replyTo, String bounceAddress, + String subject, String textBody, String htmlBody, + Map> headers, List attachments) { +} diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java index d09082cec13587..6ea0a22a4e53bd 100644 --- a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java @@ -3,6 +3,7 @@ import static java.util.Arrays.stream; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -13,10 +14,14 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import jakarta.enterprise.event.Event; + import org.jboss.logging.Logger; +import io.quarkus.arc.Arc; import io.quarkus.mailer.Attachment; import io.quarkus.mailer.Mail; +import io.quarkus.mailer.SentMail; import io.quarkus.mailer.reactive.ReactiveMailer; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; @@ -51,6 +56,7 @@ public class MutinyMailerImpl implements ReactiveMailer { private final boolean logRejectedRecipients; private final boolean logInvalidRecipients; + private final Event sentEmailEvent; MutinyMailerImpl(Vertx vertx, MailClient client, MockMailboxImpl mockMailbox, String from, String bounceAddress, boolean mock, List approvedRecipients, @@ -64,6 +70,11 @@ public class MutinyMailerImpl implements ReactiveMailer { this.approvedRecipients = approvedRecipients; this.logRejectedRecipients = logRejectedRecipients; this.logInvalidRecipients = logInvalidRecipients; + if (Arc.container() != null) { + this.sentEmailEvent = Arc.container().beanManager().getEvent().select(SentMail.class); + } else { + this.sentEmailEvent = null; + } } @Override @@ -128,13 +139,33 @@ private Uni send(Mail mail, MailMessage message) { message.getCc(), message.getBcc(), message.getText() == null ? "" : message.getText(), message.getHtml() == null ? "" : message.getHtml()); - return mockMailbox.send(mail, message); + return mockMailbox.send(mail, message) + .invoke(() -> fire(mail, message)); } else { return client.sendMail(message) + .invoke(() -> fire(mail, message)) .replaceWithVoid(); } } + private Map> copy(MultiMap headers) { + return headers.entries().stream() + .collect( + Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList()))); + } + + private void fire(Mail mail, MailMessage message) { + if (sentEmailEvent != null) { + SentMail sentMail = new SentMail(message.getFrom(), + Collections.unmodifiableList(message.getTo()), Collections.unmodifiableList(message.getCc()), + Collections.unmodifiableList(message.getBcc()), + mail.getReplyTo(), message.getBounceAddress(), + message.getSubject(), message.getText(), message.getHtml(), + copy(message.getHeaders()), Collections.unmodifiableList(mail.getAttachments())); + sentEmailEvent.fire(sentMail); + } + } + private Uni toMailMessage(Mail mail) { MailMessage message = new MailMessage();