fr.sii.ogham.email.builder.EmailBuilder Maven / Gradle / Ivy
package fr.sii.ogham.email.builder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.sii.ogham.core.builder.Builder;
import fr.sii.ogham.core.builder.ContentTranslatorBuilder;
import fr.sii.ogham.core.builder.MessageFillerBuilder;
import fr.sii.ogham.core.builder.MessagingSenderBuilder;
import fr.sii.ogham.core.builder.TemplateBuilder;
import fr.sii.ogham.core.condition.AndCondition;
import fr.sii.ogham.core.condition.Condition;
import fr.sii.ogham.core.condition.OrCondition;
import fr.sii.ogham.core.condition.RequiredClassCondition;
import fr.sii.ogham.core.condition.RequiredPropertyCondition;
import fr.sii.ogham.core.exception.builder.BuildException;
import fr.sii.ogham.core.filler.MessageFiller;
import fr.sii.ogham.core.filler.SubjectFiller;
import fr.sii.ogham.core.message.Message;
import fr.sii.ogham.core.sender.ConditionalSender;
import fr.sii.ogham.core.sender.ContentTranslatorSender;
import fr.sii.ogham.core.sender.FillerSender;
import fr.sii.ogham.core.sender.MessageSender;
import fr.sii.ogham.core.sender.MultiImplementationSender;
import fr.sii.ogham.core.translator.content.ContentTranslator;
import fr.sii.ogham.core.translator.resource.AttachmentResourceTranslator;
import fr.sii.ogham.core.util.BuilderUtils;
import fr.sii.ogham.email.EmailConstants;
import fr.sii.ogham.email.EmailConstants.SendGridConstants;
import fr.sii.ogham.email.sender.AttachmentResourceTranslatorSender;
import fr.sii.ogham.email.sender.EmailSender;
import fr.sii.ogham.template.TemplateConstants;
/**
*
* Specialized builder for email sender.
*
* There exists several implementations to send an email:
*
* - Using pure Java mail API
* - Using Apache
* Commons Email
* - Using any other library
* - Through SendGrid
* - Through a WebService
* - ...
*
*
* This builder provides a {@link MultiImplementationSender}. The aim of the
* {@link MultiImplementationSender} is to choose the best implementation for
* sending the email according to the runtime environment (detection of
* libraries in the classpath, availability of a particular property, ...).
*
*
* This builder lets you the possibility to register any new implementation. It
* allows you to enable or not templating support and automatic filling of
* message values (like sender address for example).
*
*
* @author Aurélien Baudet
* @see EmailSender
* @see JavaMailBuilder
* @see SendGridBuilder
* @see TemplateBuilder
* @see AttachmentResourceTranslatorBuilder
* @see ContentTranslatorBuilder
* @see MessageFillerBuilder
*/
public class EmailBuilder implements MessagingSenderBuilder {
private static final Logger LOG = LoggerFactory.getLogger(EmailBuilder.class);
/**
* The sender instance constructed by this builder
*/
private ConditionalSender sender;
/**
* The specialized {@link MultiImplementationSender}. It is useful to
* register new implementations
*/
private EmailSender emailSender;
/**
* The builder for message filler used to add values to the message
*/
private MessageFillerBuilder messageFillerBuilder;
/**
* The builder for the translator that will update the content of the
* message
*/
private ContentTranslatorBuilder contentTranslatorBuilder;
/**
* The builder for the resource translator to handle email attachments
*/
private AttachmentResourceTranslatorBuilder resourceTranslatorBuilder;
/**
* Map that stores email implementations indexed by associated condition
*/
private Map, Builder extends MessageSender>> implementations;
/**
* Own property key for template resolution prefix
*/
private String templatePrefixKey;
/**
* Own property key for template resolution prefix
*/
private String templateSuffixKey;
public EmailBuilder() {
super();
emailSender = new EmailSender();
sender = emailSender;
implementations = new HashMap<>();
}
@Override
public ConditionalSender build() throws BuildException {
for (Entry, Builder extends MessageSender>> impl : implementations.entrySet()) {
MessageSender s = impl.getValue().build();
LOG.debug("Implementation {} registered", s);
emailSender.addImplementation(impl.getKey(), s);
}
if (messageFillerBuilder != null) {
MessageFiller messageFiller = messageFillerBuilder.build();
LOG.debug("Automatic filling of message enabled {}", messageFiller);
sender = new FillerSender(messageFiller, sender);
}
if (resourceTranslatorBuilder != null) {
AttachmentResourceTranslator resourceTranslator = resourceTranslatorBuilder.build();
LOG.debug("Resource translation enabled {}", resourceTranslator);
sender = new AttachmentResourceTranslatorSender(resourceTranslator, sender);
}
if (contentTranslatorBuilder != null) {
if (templatePrefixKey != null) {
LOG.debug("Use custom property key {} for prefix template resolution", templatePrefixKey);
getTemplateBuilder().setPrefixKey(templatePrefixKey);
}
if (templateSuffixKey != null) {
LOG.debug("Use custom property key {} for suffix template resolution", templateSuffixKey);
getTemplateBuilder().setSuffixKey(templateSuffixKey);
}
ContentTranslator contentTranslator = contentTranslatorBuilder.build();
LOG.debug("Content translation enabled {}", contentTranslator);
sender = new ContentTranslatorSender(contentTranslator, sender);
}
return sender;
}
/**
* Tells the builder to use all default behaviors and values:
*
* - Uses Java mail default behaviors and values
* - Registers Java mail API implementation
* - Enables automatic filling of message based on configuration
* properties
* - Enables templating support
* - Enables attachment features (see {@link #withAttachmentFeatures()})
*
*
* Configuration values come from system properties.
*
*
* @return this instance for fluent use
*/
public EmailBuilder useDefaults() {
return useDefaults(BuilderUtils.getDefaultProperties());
}
/**
* Tells the builder to use all default behaviors and values:
*
* - Uses Java mail default behaviors and values
* - Registers Java mail API implementation
* - Enables automatic filling of message based on configuration
* properties
* - Enables templating support
* - Enables attachment features (see {@link #withAttachmentFeatures()})
*
*
* Configuration values come from provided properties.
*
*
* @param properties
* the properties to use instead of default ones
* @return this instance for fluent use
*/
public EmailBuilder useDefaults(Properties properties) {
registerDefaultImplementations(properties);
withAutoFilling(properties);
withTemplate(properties);
enableEmailTemplateKeys();
withAttachmentFeatures();
return this;
}
/**
* Register a new implementation for sending email. The implementation is
* associated to a condition. If the condition evaluation returns true at
* runtime then it means that the implementation can be used. If several
* implementations are available, only the first implementation is really
* invoked.
*
* @param condition
* the condition that indicates at runtime if the implementation
* can be used or not
* @param implementation
* the implementation to register
* @return this instance for fluent use
*/
public EmailBuilder registerImplementation(Condition condition, MessageSender implementation) {
emailSender.addImplementation(condition, implementation);
return this;
}
/**
* Register a new implementation for sending email. The implementation is
* associated to a condition. If the condition evaluation returns true at
* runtime then it means that the implementation can be used. If several
* implementations are available, only the first implementation is really
* invoked.
*
* @param condition
* the condition that indicates at runtime if the implementation
* can be used or not
* @param builder
* the builder for the implementation to register
* @return this instance for fluent use
*/
public EmailBuilder registerImplementation(Condition condition, Builder extends MessageSender> builder) {
implementations.put(condition, builder);
return this;
}
/**
* Register all default implementations:
*
* - Java mail API implementation
*
*
* Configuration values come from system properties.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public EmailBuilder registerDefaultImplementations() {
return registerDefaultImplementations(BuilderUtils.getDefaultProperties());
}
/**
* Register all default implementations:
*
* - Java mail API implementation
*
*
* Configuration values come from provided properties.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param properties
* the properties to use
* @return this instance for fluent use
*/
public EmailBuilder registerDefaultImplementations(Properties properties) {
withJavaMail(properties);
withSendGrid(properties);
return this;
}
/**
* Enable Java Mail API implementation. This implementation is used only if
* the associated condition indicates that Java Mail API can be used. The
* condition checks if:
*
* - The property
mail.smtp.host
is set
* - The class
javax.mail.Transport
(Java Mail API) is
* available in the classpath
* - The class
com.sun.mail.smtp.SMTPTransport
(Java Mail
* implementation) is available in the classpath
*
* The registration can silently fail if the javax.mail jar is not in the
* classpath. In this case, the Java Mail API is not registered at all.
*
* @param properties
* the properties used to check if property exists
* @return this builder instance for fluent use
*/
public EmailBuilder withJavaMail(Properties properties) {
// Java Mail API can be used only if the property "mail.smtp.host" is
// provided and also if the class "javax.mail.Transport" is defined in
// the classpath. The try/catch clause is mandatory in order to prevent
// failure when javax.mail jar is not in the classpath
try {
// @formatter:off
registerImplementation(new AndCondition<>(
new OrCondition<>(
new RequiredPropertyCondition("mail.smtp.host", properties),
new RequiredPropertyCondition("mail.host", properties)),
new RequiredClassCondition("javax.mail.Transport"),
new RequiredClassCondition("com.sun.mail.smtp.SMTPTransport")),
new JavaMailBuilder().useDefaults(properties));
// @formatter:on
} catch (Throwable e) {
LOG.debug("Can't register Java Mail implementation", e);
}
return this;
}
/**
* Enable SendGrid implementation. This implementation is used only if the
* associated condition indicates that Java Mail API can be used. The
* condition checks if:
*
* - The property
ogham.email.sendgrid.api.key
is set
* - The property
ogham.email.sendgrid.username
and
* ogham.email.sendgrid.password
is set
* - The class
com.sendgrid.SendGrid
is available in the
* classpath
*
* The registration can silently fail if the javax.mail jar is not in the
* classpath. In this case, the SendGrid is not registered at all.
*
* @param properties
* the properties used to check if property exists
* @return this builder instance for fluent use
*/
public EmailBuilder withSendGrid(Properties properties) {
// SendGrid can be used only if the property "sendgrid.api.key" is
// provided and also if the class "com.sendgrid.SendGrid" is defined in
// the classpath. The try/catch clause is mandatory in order to prevent
// failure when sendgrid jar is not in the classpath
try {
// @formatter:off
registerImplementation(new AndCondition<>(
new OrCondition<>(
new RequiredPropertyCondition(SendGridConstants.API_KEY, properties),
new AndCondition<>(
new RequiredPropertyCondition(SendGridConstants.USERNAME, properties),
new RequiredPropertyCondition(SendGridConstants.PASSWORD, properties))),
new RequiredClassCondition("com.sendgrid.SendGrid")),
new SendGridBuilder().useDefaults(properties));
// @formatter:on
} catch (Throwable e) {
LOG.debug("Can't register SendGrid implementation", e);
}
return this;
}
/**
* Enables automatic filling of emails with values that come from multiple
* sources. It let you use your own builder instead of using default
* behaviors.
*
* @param builder
* the builder for constructing the message filler
* @return this instance for fluent use
*/
public EmailBuilder withAutoFilling(MessageFillerBuilder builder) {
messageFillerBuilder = builder;
return this;
}
/**
* Enables automatic filling of emails with values that come from multiple
* sources:
*
* - Fill email with values that come from provided configuration
* properties.
* - Generate subject for the email (see {@link SubjectFiller})
*
* See {@link MessageFillerBuilder#useDefaults(Properties, String...)} for
* more information.
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param props
* the properties that contains the values to set on the email
* @param baseKeys
* the prefix(es) for the keys used for filling the message
* @return this instance for fluent use
*/
public EmailBuilder withAutoFilling(Properties props, String... baseKeys) {
withAutoFilling(new MessageFillerBuilder().useDefaults(props, baseKeys));
return this;
}
/**
* Enables automatic filling of emails with values that come from multiple
* sources:
*
* - Fill email with values that come from provided configuration
* properties. It uses the default prefix for the keys ("mail" and
* "ogham.email").
* - Generate subject for the email (see {@link SubjectFiller})
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param props
* the properties that contains the values to set on the email
* @return this instance for fluent use
*/
public EmailBuilder withAutoFilling(Properties props) {
return withAutoFilling(props, EmailConstants.FILL_PREFIXES);
}
/**
* Enables automatic filling of emails with values that come from multiple
* sources:
*
* - Fill email with values that come from system configuration
* properties. It uses the default prefix for the keys ("ogham.email").
* - Generate subject for the email (see {@link SubjectFiller})
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public EmailBuilder withAutoFilling() {
return withAutoFilling(BuilderUtils.getDefaultProperties());
}
/**
* Enables templating support using all default behaviors and values. See
* {@link ContentTranslatorBuilder#useDefaults()} for more information.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public EmailBuilder withTemplate() {
return withTemplate(BuilderUtils.getDefaultProperties());
}
/**
* Enables templating support using all default behaviors and values. See
* {@link ContentTranslatorBuilder#useDefaults()} for more information.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param properties
* the properties to use
* @return this instance for fluent use
*/
public EmailBuilder withTemplate(Properties properties) {
return withTemplate(new ContentTranslatorBuilder().useDefaults(properties));
}
/**
* Enables templating support using the provided
* {@link ContentTranslatorBuilder}. It decorates the email sender with a
* {@link ContentTranslatorSender}.
*
* @param builder
* the builder to use for constructing the
* {@link ContentTranslator} instead of using the default one
* @return this instance for fluent use
*/
public EmailBuilder withTemplate(ContentTranslatorBuilder builder) {
this.contentTranslatorBuilder = builder;
return this;
}
/**
*
* Calling this method will enable different location for email templates
* from default one. The location will be specified by different property
* keys for prefix and suffix.
*
*
* By default default properties are:
*
* - ogham.template.prefix (see {@link TemplateConstants#PREFIX_PROPERTY})
*
* - ogham.template.suffix (see {@link TemplateConstants#SUFFIX_PROPERTY}
*
*
* Calling this method will change the property keys to:
*
* - ogham.email.template.prefix (see
* {@link fr.sii.ogham.email.EmailConstants.TemplateConstants#PREFIX_PROPERTY}
*
* - ogham.email.template.suffix (see
* {@link fr.sii.ogham.email.EmailConstants.TemplateConstants#SUFFIX_PROPERTY}
*
*
*
* @return this instance for fluent use
*/
public EmailBuilder enableEmailTemplateKeys() {
setTemplatePrefixKey(EmailConstants.TemplateConstants.PREFIX_PROPERTY);
setTemplateSuffixKey(EmailConstants.TemplateConstants.SUFFIX_PROPERTY);
return this;
}
/**
*
* Calling this method will enable different location for email templates
* from default one. The location will be specified by a different property
* key for prefix.
*
*
*
* By default default property key is ogham.template.prefix (see
* {@link TemplateConstants#PREFIX_PROPERTY})
*
*
*
* Calling this method will change the property key to the provided key.
*
*
* @param prefixKey
* the new key for the email template prefix
* @return this instance for fluent use
*/
public EmailBuilder setTemplatePrefixKey(String prefixKey) {
this.templatePrefixKey = prefixKey;
return this;
}
/**
*
* Calling this method will enable different location for email templates
* from default one. The location will be specified by a different property
* key for suffix.
*
*
*
* By default default property key is ogham.template.prefix (see
* {@link TemplateConstants#SUFFIX_PROPERTY})
*
*
*
* Calling this method will change the property key to the provided key.
*
*
* @param suffixKey
* the new key for the email template suffix
* @return this instance for fluent use
*/
public EmailBuilder setTemplateSuffixKey(String suffixKey) {
this.templateSuffixKey = suffixKey;
return this;
}
/**
* Enable attachment features like attachment resolution based on lookup
* mapping. It delegates to {@link AttachmentResourceTranslatorBuilder} with
* the default behavior and values (see
* {@link AttachmentResourceTranslatorBuilder#useDefaults()}).
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public EmailBuilder withAttachmentFeatures() {
return withAttachmentFeatures(new AttachmentResourceTranslatorBuilder().useDefaults());
}
/**
* Enable attachment features using the provided translator builder.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param builder
* the builder for the translator to use instead of the default
* one
* @return this instance for fluent use
*/
public EmailBuilder withAttachmentFeatures(AttachmentResourceTranslatorBuilder builder) {
resourceTranslatorBuilder = builder;
return this;
}
/**
*
* Get reference to the specialized builder. It may be useful to fine tune a
* specific implementation.
*
*
* There also exists shortcuts: {@link #getJavaMailBuilder()} and
* {@link #getSendGridBuilder()}.
*
*
* @param clazz
* the class of the builder to get
* @param
* the type of the class to get
* @return the builder instance for the specific implementation
* @throws IllegalArgumentException
* when provided class references an nonexistent builder
*/
@SuppressWarnings("unchecked")
public > B getImplementationBuilder(Class clazz) {
for (Builder extends MessageSender> builder : implementations.values()) {
if (clazz.isAssignableFrom(builder.getClass())) {
return (B) builder;
}
}
throw new IllegalArgumentException("No implementation builder exists for " + clazz.getSimpleName());
}
/**
*
* Get the reference to the specialized builder for Java Mail API. It may be
* useful to fine tune Java Mail API implementation.
*
*
* Access this builder if you want to:
*
* - Customize content handler management
* - Customize resource attachment handler management
* - Customize Mimetype detection
* - Use custom Authenticator
* - Use custom interceptor
*
*
* @return The specialized builder for Java Mail API
*/
public JavaMailBuilder getJavaMailBuilder() {
return getImplementationBuilder(JavaMailBuilder.class);
}
/**
*
* Get the reference to the specialized builder for SendGrid. It may be
* useful to fine tune SendGrid implementation.
*
*
* Access this builder if you want to:
*
* - Customize content handler management
* - Customize Mimetype detection
* - Customize username/password/API key
* - Provide your own SendGrid client implementation
*
*
* @return The specialized builder for SendGrid
*/
public SendGridBuilder getSendGridBuilder() {
return getImplementationBuilder(SendGridBuilder.class);
}
/**
*
* Get the builder used for filling messages.
*
*
* Access this builder if you want to:
*
* - Enable/disable automatic filling of messages with values provided in
* configuration
* - Enable/disable automatic filling of subject for messages based on
* templates
* - Add your own message filler
*
*
* @return the builder used for filling messages
*/
public MessageFillerBuilder getMessageFillerBuilder() {
return messageFillerBuilder;
}
/**
*
* Get the builder used transform the content of the message. It may be
* useful to fine tune templating mechanism, resource inlining and messages
* with with several contents.
*
*
* Access this builder if you want to:
*
* - Customize templating mechanism (see {@link #getTemplateBuilder()})
* - Enable/disable support for messages with multiple contents
* - Enable/disable support for inlining of resources
* - Add your own content translator
*
*
* @return the builder used to transform the content of the message
*/
public ContentTranslatorBuilder getContentTranslatorBuilder() {
return contentTranslatorBuilder;
}
/**
*
* Shortcut to directly access template builder for fine tuning templating
* mechanism.
*
*
* Access this builder if you want to:
*
* - Customize how template resources are resolved
* - Register a custom lookup mapping resolver for template resources
* - Use your own template engine
* - Customize the template engine configuration
* - Set the prefix and suffix for template resolution
* - Set the property key for prefix and suffix resolution
*
*
* @return the template builder
*/
public TemplateBuilder getTemplateBuilder() {
return contentTranslatorBuilder.getTemplateBuilder();
}
/**
*
* Get the builder used to transform the resources associated to the
* message. It may be useful to fine tune how to attach resources to
* messages.
*
*
* Access this builder if you want to:
*
* - Customize how attached resources are transformed
* - Customize how attached resources are resolved for transformation
* - Register a custom lookup mapping resolver for attached resources
*
*
* @return the builder used to transform the resources associated to the
* message
*/
public AttachmentResourceTranslatorBuilder getResourceTranslatorBuilder() {
return resourceTranslatorBuilder;
}
}