fr.sii.ogham.email.builder.sendgrid.SendGridBuilder Maven / Gradle / Ivy
Show all versions of ogham-email-sendgrid Show documentation
package fr.sii.ogham.email.builder.sendgrid;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.activation.MimetypesFileTypeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sendgrid.SendGrid;
import fr.sii.ogham.core.builder.AbstractParent;
import fr.sii.ogham.core.builder.Builder;
import fr.sii.ogham.core.builder.MessagingBuilder;
import fr.sii.ogham.core.builder.env.EnvironmentBuilder;
import fr.sii.ogham.core.builder.env.EnvironmentBuilderDelegate;
import fr.sii.ogham.core.builder.env.SimpleEnvironmentBuilder;
import fr.sii.ogham.core.builder.mimetype.MimetypeDetectionBuilder;
import fr.sii.ogham.core.builder.mimetype.MimetypeDetectionBuilderDelegate;
import fr.sii.ogham.core.builder.mimetype.SimpleMimetypeDetectionBuilder;
import fr.sii.ogham.core.env.PropertyResolver;
import fr.sii.ogham.core.message.content.MultiContent;
import fr.sii.ogham.core.message.content.StringContent;
import fr.sii.ogham.core.mimetype.MimeTypeProvider;
import fr.sii.ogham.core.util.BuilderUtils;
import fr.sii.ogham.email.builder.EmailBuilder;
import fr.sii.ogham.email.message.Email;
import fr.sii.ogham.email.sender.impl.SendGridSender;
import fr.sii.ogham.email.sender.impl.sendgrid.client.DelegateSendGridClient;
import fr.sii.ogham.email.sender.impl.sendgrid.client.SendGridClient;
import fr.sii.ogham.email.sender.impl.sendgrid.client.SendGridInterceptor;
import fr.sii.ogham.email.sender.impl.sendgrid.handler.MapContentHandler;
import fr.sii.ogham.email.sender.impl.sendgrid.handler.MultiContentHandler;
import fr.sii.ogham.email.sender.impl.sendgrid.handler.StringContentHandler;
/**
* Configures how SendGrid implementation will send {@link Email}s.
*
* This implementation uses SendGrid HTTP API.
*
*
* To send {@link Email} using SendGrid, you need to register this builder into
* a {@link MessagingBuilder} like this:
*
*
*
* MessagingBuilder msgBuilder = ...
* msgBuilder.email()
* .sender(SendGridBuilder.class) // registers the builder and accesses to that builder for configuring it
*
*
*
* Once the builder is registered, sending email through SendGrid requires
* either an API key or a username/password pair. You can define it using:
*
*
*
* msgBuilder.email()
* .sender(SendGridBuilder.class) // registers the builder and accesses to that builder for configuring it
* .apiKey("foo")
*
*
*
* Or you can also use property keys (using interpolation):
*
*
*
* msgBuilder
* .environment()
* .properties()
* .set("custom.property.for.api-key", "foo")
* .and()
* .and()
* .email()
* .sender(SendGridBuilder.class) // registers the builder and accesses to that builder for configuring it
* .host("${custom.property.for.api-key}")
*
*
*
*
* Finally, Ogham will transform general {@link Email} object into
* {@link SendGrid}.Email object. This transformation will fit almost all use
* cases but you may need to customize a part of the SendGrid message. Instead
* of doing again the same work Ogham does, this builder allows you to intercept
* the message to modify it just before sending it:
*
*
*
* .sender(SendGridBuilder.class)
* .intercept(new MyCustomInterceptor())
*
*
*
* See {@link SendGridInterceptor} for more information.
*
* @author Aurélien Baudet
*
*/
public class SendGridBuilder extends AbstractParent implements Builder {
private static final Logger LOG = LoggerFactory.getLogger(SendGridBuilder.class);
private EnvironmentBuilder environmentBuilder;
private MimetypeDetectionBuilder mimetypeBuilder;
private List apiKeys;
private List usernames;
private List passwords;
private SendGridClient client;
private SendGridInterceptor interceptor;
/**
* Default constructor when using SendGrid sender without all Ogham work.
*
* WARNING: use is only if you know what you are doing !
*/
public SendGridBuilder() {
this(null);
environment();
mimetype();
}
/**
* Constructor that is called when using Ogham builder:
*
*
* MessagingBuilder msgBuilder = ...
* msgBuilder
* .email()
* .sender(SendGridBuilder.class)
*
*
* @param parent
* the parent builder instance for fluent chaining
*/
public SendGridBuilder(EmailBuilder parent) {
super(parent);
apiKeys = new ArrayList<>();
usernames = new ArrayList<>();
passwords = new ArrayList<>();
}
/**
* Set SendGrid API
* key.
*
* You can specify a direct value. For example:
*
*
* .apiKey("localhost");
*
*
*
* You can also specify one or several property keys. For example:
*
*
* .apiKey("${custom.property.high-priority}", "${custom.property.low-priority}");
*
*
* The properties are not immediately evaluated. The evaluation will be done
* when the {@link #build()} method is called.
*
* If you provide several property keys, evaluation will be done on the
* first key and if the property exists (see {@link EnvironmentBuilder}),
* its value is used. If the first property doesn't exist in properties,
* then it tries with the second one and so on.
*
* @param key
* one value, or one or several property keys
* @return this instance for fluent chaining
*/
public SendGridBuilder apiKey(String... key) {
for (String k : key) {
if (k != null) {
apiKeys.add(k);
}
}
return this;
}
/**
* Set username for SendGrid HTTP API.
*
* You can specify a direct value. For example:
*
*
* .username("foo");
*
*
*
* You can also specify one or several property keys. For example:
*
*
* .username("${custom.property.high-priority}", "${custom.property.low-priority}");
*
*
* The properties are not immediately evaluated. The evaluation will be done
* when the {@link #build()} method is called.
*
* If you provide several property keys, evaluation will be done on the
* first key and if the property exists (see {@link EnvironmentBuilder}),
* its value is used. If the first property doesn't exist in properties,
* then it tries with the second one and so on.
*
* @param username
* one value, or one or several property keys
* @return this instance for fluent chaining
*/
public SendGridBuilder username(String... username) {
for (String u : username) {
if (u != null) {
usernames.add(u);
}
}
return this;
}
/**
* Set password for SendGrid HTTP API.
*
* You can specify a direct value. For example:
*
*
* .password("foo");
*
*
*
* You can also specify one or several property keys. For example:
*
*
* .password("${custom.property.high-priority}", "${custom.property.low-priority}");
*
*
* The properties are not immediately evaluated. The evaluation will be done
* when the {@link #build()} method is called.
*
* If you provide several property keys, evaluation will be done on the
* first key and if the property exists (see {@link EnvironmentBuilder}),
* its value is used. If the first property doesn't exist in properties,
* then it tries with the second one and so on.
*
* @param password
* one value, or one or several property keys
* @return this instance for fluent chaining
*/
public SendGridBuilder password(String... password) {
for (String p : password) {
if (p != null) {
passwords.add(p);
}
}
return this;
}
/**
* By default, calling SendGrid HTTP API is done through the default
* {@link SendGrid} implementation. If you want to use another client
* implementation (creating your custom HTTP API caller for example), you
* can implement the {@link SendGridClient} interface and provide it:
*
*
* .client(new MyCustomHttpApiCaller())
*
*
* NOTE: if you provide your custom implementation, any defined properties
* and values using {@link #apiKey(String...)}, {@link #username(String...)}
* or {@link #password(String...)} won't be used at all. You then have to
* handle it by yourself.
*
* @param client
* the custom client used to call SendGrid HTTP API
* @return this instance for fluent chaining
*/
public SendGridBuilder client(SendGridClient client) {
this.client = client;
return this;
}
/**
* Builder that configures mimetype detection.
*
* There exists several implementations to provide the mimetype:
*
* - Using Java {@link MimetypesFileTypeMap}
* - Using Java 7 {@link Files#probeContentType(java.nio.file.Path)}
* - Using Apache Tika
* - Using
* JMimeMagic
*
*
*
* Both implementations provided by Java are based on file extensions. This
* can't be used in most cases as we often handle {@link InputStream}s.
*
*
*
* In previous version of Ogham, JMimeMagic was used and was working quite
* well. Unfortunately, the library is no more maintained.
*
*
*
* You can configure how Tika will detect mimetype:
*
*
* .mimetype()
* .tika()
* ...
*
*
*
* This builder allows to use several providers. It will chain them until
* one can find a valid mimetype. If none is found, you can explicitly
* provide the default one:
*
*
* .mimetype()
* .defaultMimetype("text/html")
*
*
*
* If no mimetype detector was previously defined, it creates a new one.
* Then each time you call {@link #mimetype()}, the same instance is used.
*
*
* @return the builder to configure mimetype detection
*/
public MimetypeDetectionBuilder mimetype() {
if (mimetypeBuilder == null) {
mimetypeBuilder = new SimpleMimetypeDetectionBuilder<>(this, environmentBuilder);
}
return mimetypeBuilder;
}
/**
* NOTE: this is mostly for advance usage (when creating a custom module).
*
* Inherits mimetype configuration from another builder. This is useful for
* configuring independently different parts of Ogham but keeping a whole
* coherence (see {@link DefaultSendGridConfigurer} for an example of use).
*
* The same instance is shared meaning that all changes done here will also
* impact the other builder.
*
*
* If a previous builder was defined (by calling {@link #mimetype()} for
* example), the new builder will override it.
*
* @param builder
* the builder to inherit
* @return this instance for fluent chaining
*/
public SendGridBuilder mimetype(MimetypeDetectionBuilder> builder) {
mimetypeBuilder = new MimetypeDetectionBuilderDelegate<>(this, builder);
return this;
}
/**
* Configures environment for the builder (and sub-builders). Environment
* consists of configuration properties/values that are used to configure
* the system (see {@link EnvironmentBuilder} for more information).
*
* You can use system properties:
*
*
* .environment()
* .systemProperties();
*
*
* Or, you can load properties from a file:
*
*
* .environment()
* .properties("/path/to/file.properties")
*
*
* Or using directly a {@link Properties} object:
*
*
* Properties myprops = new Properties();
* myprops.setProperty("foo", "bar");
* .environment()
* .properties(myprops)
*
*
* Or defining directly properties:
*
*
* .environment()
* .properties()
* .set("foo", "bar")
*
*
*
*
* If no environment was previously used, it creates a new one. Then each
* time you call {@link #environment()}, the same instance is used.
*
*
* @return the builder to configure properties handling
*/
public EnvironmentBuilder environment() {
if (environmentBuilder == null) {
environmentBuilder = new SimpleEnvironmentBuilder<>(this);
}
return environmentBuilder;
}
/**
* NOTE: this is mostly for advance usage (when creating a custom module).
*
* Inherits environment configuration from another builder. This is useful
* for configuring independently different parts of Ogham but keeping a
* whole coherence (see {@link DefaultSendGridConfigurer} for an example of
* use).
*
* The same instance is shared meaning that all changes done here will also
* impact the other builder.
*
*
* If a previous builder was defined (by calling {@link #environment()} for
* example), the new builder will override it.
*
* @param builder
* the builder to inherit
* @return this instance for fluent chaining
*/
public SendGridBuilder environment(EnvironmentBuilder> builder) {
environmentBuilder = new EnvironmentBuilderDelegate<>(this, builder);
return this;
}
/**
* Ogham will transform general {@link Email} object into
* {@link SendGrid}.Email objects. This transformation will fit almost all
* use cases but you may need to customize a part of the SendGrid message.
* Instead of doing again the same work Ogham does, this builder allows you
* to intercept the message to modify it just before sending it:
*
*
* .sender(SendGridBuilder.class)
* .intercept(new MyCustomInterceptor())
*
*
* See {@link SendGridInterceptor} for more information.
*
* @param interceptor
* the custom interceptor used to modify {@link SendGrid}.Email
* @return this instance for fluent chaining
*/
public SendGridBuilder intercept(SendGridInterceptor interceptor) {
this.interceptor = interceptor;
return this;
}
@Override
public SendGridSender build() {
PropertyResolver propertyResolver = environmentBuilder.build();
String apiKey = BuilderUtils.evaluate(this.apiKeys, propertyResolver, String.class);
String username = BuilderUtils.evaluate(this.usernames, propertyResolver, String.class);
String password = BuilderUtils.evaluate(this.passwords, propertyResolver, String.class);
SendGridClient builtClient = buildClient(apiKey, username, password);
if (builtClient == null) {
return null;
}
LOG.info("Sending email using SendGrid API is registered");
LOG.debug("SendGrid account: apiKey={}, username={}", apiKey, username);
return new SendGridSender(builtClient, buildContentHandler(), interceptor);
}
private SendGridClient buildClient(String apiKey, String username, String password) {
if (client != null) {
return client;
}
if (apiKey != null) {
return new DelegateSendGridClient(apiKey);
}
if (username != null && password != null) {
return new DelegateSendGridClient(username, password);
}
return null;
}
private MapContentHandler buildContentHandler() {
MimeTypeProvider mimetypeProvider = mimetypeBuilder.build();
MapContentHandler contentHandler = new MapContentHandler();
contentHandler.register(MultiContent.class, new MultiContentHandler(contentHandler));
contentHandler.register(StringContent.class, new StringContentHandler(mimetypeProvider));
return contentHandler;
}
}