Skip to content

Commit

Permalink
Add support for multiple mailers
Browse files Browse the repository at this point in the history
Fixes #9064
  • Loading branch information
gsmet committed Mar 25, 2023
1 parent d15dff6 commit fb2affe
Show file tree
Hide file tree
Showing 30 changed files with 1,230 additions and 246 deletions.
74 changes: 74 additions & 0 deletions docs/src/main/asciidoc/mailer-reference.adoc
Expand Up @@ -341,6 +341,80 @@ When not set, Quarkus tries to deduce the type from the file name.

NOTE: You can also configure `quarkus.mailer.trust-all=true` to bypass the verification.

== Multiple mailer configurations

Some applications require to send mails through different SMTP servers.

This use case is perfectly supported in Quarkus and you can configure several mailers:

[source,properties]
----
quarkus.mailer.from=your-from-address@gmail.com <1>
quarkus.mailer.host=smtp.gmail.com
quarkus.mailer.aws.from=your-from-address@gmail.com <2>
quarkus.mailer.aws.host=${ses.smtp}
quarkus.mailer.aws.port=587
quarkus.mailer.sendgrid.from=your-from-address@gmail.com <3>
quarkus.mailer.sendgrid.host=${sendgrid.smtp-host}
quarkus.mailer.sendgrid.port=465
----
<1> Configuration for the default mailer.
<2> Configuration for a mailer named `aws`.
<3> Configuration for a mailer named `sendgrid`.

Then, access your named mailers by using the `@MailerName` CDI qualifier:

[source,java]
----
@Inject <1>
Mailer mailer;
@Inject <1>
ReactiveMailer reactiveMailer;
@Inject <1>
@Location("hello")
MailTemplate mailTemplate;
@Inject
@MailerName("aws") <2>
Mailer mailer;
@Inject
@MailerName("aws") <2>
ReactiveMailer reactiveMailer;
@Inject
@MailerName("aws") <2>
@Location("hello")
MailTemplate mailTemplate;
@Inject
@MailerName("sendgrid") <3>
Mailer mailer;
@Inject
@MailerName("sendgrid") <3>
ReactiveMailer reactiveMailer;
@Inject
@MailerName("sendgrid") <3>
@Location("hello")
MailTemplate mailTemplate;
----
<1> Inject instances without qualifier for the default configuration.
<2> Inject instances with the `@MailerName("aws")` qualifier for the `aws` configuration.
<3> Inject instances with the `@MailerName("sendgrid")` qualifier for the `sendgrid` configuration.

[WARNING]
====
Type-safe template using `@CheckedTemplate` are currently only supported for the default mailer configuration.
Use `MailTemplate` injection for the named mailer configurations.
====

[[popular]]
== Mailer configuration for popular email services

Expand Down
@@ -0,0 +1,41 @@
package io.quarkus.mailer.deployment;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.DotName;

import io.quarkus.arc.processor.AnnotationsTransformer;

public class MailTemplateMailerNameTransformer implements AnnotationsTransformer {

@Override
public boolean appliesTo(Kind kind) {
return Kind.FIELD == kind || Kind.METHOD_PARAMETER == kind;
}

@Override
public void transform(TransformationContext transformationContext) {
DotName type;

if (transformationContext.getTarget().kind() == Kind.FIELD) {
type = transformationContext.getTarget().asField().type().name();
} else {
type = transformationContext.getTarget().asMethodParameter().type().name();
}

if (!MailerProcessor.MAIL_TEMPLATE.equals(type)) {
return;
}

AnnotationInstance mailerName = transformationContext.getTarget().annotation(MailerProcessor.MAILER_NAME);
if (mailerName == null) {
return;
}

transformationContext.transform()
.remove(ai -> MailerProcessor.MAILER_NAME.equals(ai.name()))
.add(MailerProcessor.MAIL_TEMPLATE_MAILER_NAME, mailerName.value())
.done();
}

}
Expand Up @@ -5,42 +5,69 @@
import java.util.List;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;

import jakarta.enterprise.inject.Default;
import jakarta.inject.Singleton;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.mailer.MailTemplate;
import io.quarkus.mailer.runtime.BlockingMailerImpl;
import io.quarkus.mailer.runtime.MailBuildTimeConfig;
import io.quarkus.mailer.runtime.MailClientProducer;
import io.quarkus.mailer.Mailer;
import io.quarkus.mailer.MailerName;
import io.quarkus.mailer.MockMailbox;
import io.quarkus.mailer.reactive.ReactiveMailer;
import io.quarkus.mailer.runtime.MailTemplateMailerName;
import io.quarkus.mailer.runtime.MailTemplateProducer;
import io.quarkus.mailer.runtime.MailerSupportProducer;
import io.quarkus.mailer.runtime.MockMailboxImpl;
import io.quarkus.mailer.runtime.MutinyMailerImpl;
import io.quarkus.mailer.runtime.MailerRecorder;
import io.quarkus.mailer.runtime.MailerSupport;
import io.quarkus.mailer.runtime.Mailers;
import io.quarkus.mailer.runtime.MailersBuildTimeConfig;
import io.quarkus.mailer.runtime.MailersRuntimeConfig;
import io.quarkus.qute.deployment.CheckedTemplateAdapterBuildItem;
import io.quarkus.qute.deployment.QuteProcessor;
import io.quarkus.qute.deployment.TemplatePathBuildItem;
import io.vertx.ext.mail.MailClient;

public class MailerProcessor {

private static final DotName MAIL_TEMPLATE = DotName.createSimple(MailTemplate.class.getName());
static final DotName MAIL_TEMPLATE = DotName.createSimple(MailTemplate.class.getName());

static final DotName MAILER_NAME = DotName.createSimple(MailerName.class);

static final DotName MAIL_TEMPLATE_MAILER_NAME = DotName.createSimple(MailTemplateMailerName.class);

private static final List<DotName> SUPPORTED_INJECTION_TYPES = List.of(
DotName.createSimple(Mailer.class),
DotName.createSimple(ReactiveMailer.class),
DotName.createSimple(MockMailbox.class),
DotName.createSimple(MailClient.class),
DotName.createSimple(io.vertx.mutiny.ext.mail.MailClient.class),
MAIL_TEMPLATE);

public static class CacheAttachmentsEnabled implements BooleanSupplier {
MailBuildTimeConfig config;
MailersBuildTimeConfig config;

public boolean getAsBoolean() {
return config.cacheAttachments;
Expand All @@ -50,12 +77,116 @@ public boolean getAsBoolean() {
@BuildStep
void registerBeans(BuildProducer<AdditionalBeanBuildItem> beans) {
beans.produce(AdditionalBeanBuildItem.builder().setUnremovable()
.addBeanClasses(MutinyMailerImpl.class, BlockingMailerImpl.class,
MailClientProducer.class, MailerSupportProducer.class)
.addBeanClasses(Mailers.class)
.build());
beans.produce(AdditionalBeanBuildItem.builder()
.addBeanClasses(MockMailboxImpl.class, MailTemplateProducer.class)
.addBeanClasses(MailTemplateProducer.class)
.build());
// add the @MailerName class otherwise it won't be registered as a qualifier
beans.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(MailerName.class)
.build());
}

@Record(ExecutionTime.STATIC_INIT)
@BuildStep
MailersBuildItem generateMailerSupportBean(MailerRecorder recorder,
BeanDiscoveryFinishedBuildItem beans,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
List<InjectionPointInfo> mailerInjectionPoints = beans.getInjectionPoints().stream()
.filter(i -> SUPPORTED_INJECTION_TYPES.contains(i.getRequiredType().name()))
.collect(Collectors.toList());

boolean hasDefaultMailer = mailerInjectionPoints.stream().anyMatch(i -> i.hasDefaultedQualifier());

Set<String> namedMailers = mailerInjectionPoints.stream()
.map(i -> i.getRequiredQualifier(MAILER_NAME))
.filter(ai -> ai != null)
.map(ai -> ai.value().asString())
.collect(Collectors.toSet());

MailerSupport mailerSupport = new MailerSupport(hasDefaultMailer, namedMailers);

syntheticBeans.produce(SyntheticBeanBuildItem.configure(MailerSupport.class)
.supplier(recorder.mailerSupportSupplier(mailerSupport))
.scope(Singleton.class)
.unremovable()
.done());

return new MailersBuildItem(hasDefaultMailer, namedMailers);
}

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void generateMailerBeans(MailerRecorder recorder,
MailersBuildItem mailers,
MailersRuntimeConfig mailersRuntimeConfig,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
if (mailers.hasDefaultMailer()) {
generateMailerBeansForName(Mailers.DEFAULT_MAILER_NAME, recorder, mailersRuntimeConfig, syntheticBeans);
}

for (String name : mailers.getNamedMailers()) {
generateMailerBeansForName(name, recorder, mailersRuntimeConfig, syntheticBeans);
}
}

@BuildStep
AnnotationsTransformerBuildItem annotationsTransformer() {
return new AnnotationsTransformerBuildItem(new MailTemplateMailerNameTransformer());
}

private void generateMailerBeansForName(String name,
MailerRecorder recorder,
MailersRuntimeConfig mailersRuntimeConfig,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
AnnotationInstance qualifier;
if (Mailers.DEFAULT_MAILER_NAME.equals(name)) {
qualifier = AnnotationInstance.builder(Default.class).build();
} else {
qualifier = AnnotationInstance.builder(MAILER_NAME).add("value", name).build();
}

syntheticBeans.produce(SyntheticBeanBuildItem.configure(MailClient.class)
.scope(Singleton.class)
.qualifiers(qualifier)
.unremovable()
.setRuntimeInit()
.addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class)))
.createWith(recorder.mailClientFunction(name, mailersRuntimeConfig))
.done());
syntheticBeans.produce(SyntheticBeanBuildItem.configure(io.vertx.mutiny.ext.mail.MailClient.class)
.scope(Singleton.class)
.qualifiers(qualifier)
.unremovable()
.setRuntimeInit()
.addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class)))
.createWith(recorder.reactiveMailClientFunction(name, mailersRuntimeConfig))
.done());
syntheticBeans.produce(SyntheticBeanBuildItem.configure(Mailer.class)
.scope(Singleton.class)
.qualifiers(qualifier)
.unremovable()
.setRuntimeInit()
.addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class)))
.createWith(recorder.mailerFunction(name, mailersRuntimeConfig))
.done());
syntheticBeans.produce(SyntheticBeanBuildItem.configure(ReactiveMailer.class)
.scope(Singleton.class)
.qualifiers(qualifier)
.unremovable()
.setRuntimeInit()
.addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class)))
.createWith(recorder.reactiveMailerFunction(name, mailersRuntimeConfig))
.done());
syntheticBeans.produce(SyntheticBeanBuildItem.configure(MockMailbox.class)
.scope(Singleton.class)
.qualifiers(qualifier)
.unremovable()
.setRuntimeInit()
.addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class)))
.createWith(recorder.mockMailboxFunction(name, mailersRuntimeConfig))
.done());
}

@BuildStep
Expand Down
@@ -0,0 +1,25 @@
package io.quarkus.mailer.deployment;

import java.util.Set;

import io.quarkus.builder.item.SimpleBuildItem;

final class MailersBuildItem extends SimpleBuildItem {

private final boolean hasDefaultMailer;

private final Set<String> namedMailers;

public MailersBuildItem(boolean hasDefaultMailer, Set<String> namedMailers) {
this.hasDefaultMailer = hasDefaultMailer;
this.namedMailers = namedMailers;
}

public boolean hasDefaultMailer() {
return hasDefaultMailer;
}

public Set<String> getNamedMailers() {
return namedMailers;
}
}

0 comments on commit fb2affe

Please sign in to comment.