SendGridV2Builder.java

package fr.sii.ogham.email.sendgrid.v2.builder.sendgrid;

import java.net.URL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sendgrid.SendGrid;

import fr.sii.ogham.core.builder.MessagingBuilder;
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilder;
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilderHelper;
import fr.sii.ogham.core.builder.context.BuildContext;
import fr.sii.ogham.core.builder.context.DefaultBuildContext;
import fr.sii.ogham.core.message.content.MayHaveStringContent;
import fr.sii.ogham.core.message.content.MultiContent;
import fr.sii.ogham.core.mimetype.MimeTypeProvider;
import fr.sii.ogham.email.builder.EmailBuilder;
import fr.sii.ogham.email.message.Email;
import fr.sii.ogham.email.message.content.ContentWithAttachments;
import fr.sii.ogham.email.sendgrid.builder.AbstractSendGridBuilder;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.SendGridV2Sender;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.sendgrid.client.DelegateSendGridClient;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.sendgrid.client.SendGridClient;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.sendgrid.client.SendGridInterceptor;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.sendgrid.handler.ContentWithAttachmentsHandler;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.sendgrid.handler.MultiContentHandler;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.sendgrid.handler.PriorizedContentHandler;
import fr.sii.ogham.email.sendgrid.v2.sender.impl.sendgrid.handler.StringContentHandler;

/**
 * Configures how SendGrid implementation will send {@link Email}s.
 * 
 * This implementation uses SendGrid HTTP API.
 * 
 * <p>
 * To send {@link Email} using SendGrid, you need to register this builder into
 * a {@link MessagingBuilder} like this:
 * 
 * <pre>
 * <code>
 * MessagingBuilder msgBuilder = ...
 * msgBuilder.email()
 *    .sender(SendGridV2Builder.class)    // registers the builder and accesses to that builder for configuring it
 * </code>
 * </pre>
 * 
 * Once the builder is registered, sending email through SendGrid requires
 * either an API key or a username/password pair. You can define it using:
 * 
 * <pre>
 * <code>
 * msgBuilder.email()
 *    .sender(SendGridV2Builder.class)    // registers the builder and accesses to that builder for configuring it
 *       .apiKey("foo")
 * </code>
 * </pre>
 * 
 * Or you can also use property keys (using interpolation):
 * 
 * <pre>
 * <code>
 * msgBuilder
 * .environment()
 *    .properties()
 *       .set("custom.property.for.api-key", "foo")
 *       .and()
 *    .and()
 * .email()
 *    .sender(SendGridV2Builder.class)    // registers the builder and accesses to that builder for configuring it
 *       .apiKey("${custom.property.for.api-key}")
 * </code>
 * </pre>
 * 
 * <p>
 * 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:
 * 
 * <pre>
 * <code>
 * .sender(SendGridV2Builder.class)
 *    .intercept(new MyCustomInterceptor())
 * </code>
 * </pre>
 * 
 * See {@link SendGridInterceptor} for more information.
 * 
 * @author Aurélien Baudet
 *
 */
public class SendGridV2Builder extends AbstractSendGridBuilder<SendGridV2Builder, EmailBuilder> {
	private static final Logger LOG = LoggerFactory.getLogger(SendGridV2Builder.class);

	private final ConfigurationValueBuilderHelper<SendGridV2Builder, String> usernameValueBuilder;
	private final ConfigurationValueBuilderHelper<SendGridV2Builder, String> passwordValueBuilder;
	private SendGridClient client;
	private SendGridInterceptor interceptor;

	/**
	 * Default constructor when using SendGrid sender without all Ogham work.
	 * 
	 * <strong>WARNING: use is only if you know what you are doing !</strong>
	 */
	public SendGridV2Builder() {
		this(null, new DefaultBuildContext());
	}

	/**
	 * Constructor that is called when using Ogham builder:
	 * 
	 * <pre>
	 * MessagingBuilder msgBuilder = ...
	 * msgBuilder
	 * .email()
	 *    .sender(SendGridV2Builder.class)
	 * </pre>
	 * 
	 * @param parent
	 *            the parent builder instance for fluent chaining
	 * @param buildContext
	 *            for registering instances and property evaluation
	 */
	public SendGridV2Builder(EmailBuilder parent, BuildContext buildContext) {
		super(SendGridV2Builder.class, parent, buildContext);
		usernameValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
		passwordValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
	}

	@Override
	public SendGridV2Builder username(String username) {
		usernameValueBuilder.setValue(username);
		return this;
	}

	@Override
	public ConfigurationValueBuilder<SendGridV2Builder, String> username() {
		return usernameValueBuilder;
	}

	@Override
	public SendGridV2Builder password(String password) {
		passwordValueBuilder.setValue(password);
		return this;
	}

	@Override
	public ConfigurationValueBuilder<SendGridV2Builder, String> password() {
		return passwordValueBuilder;
	}

	/**
	 * 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:
	 * 
	 * <pre>
	 * .client(new MyCustomHttpApiCaller())
	 * </pre>
	 * 
	 * 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 SendGridV2Builder client(SendGridClient client) {
		this.client = client;
		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:
	 * 
	 * <pre>
	 * .sender(SendGridV2Builder.class)
	 *    .intercept(new MyCustomInterceptor())
	 * </pre>
	 * 
	 * See {@link SendGridInterceptor} for more information.
	 * 
	 * @param interceptor
	 *            the custom interceptor used to modify {@link SendGrid}.Email
	 * @return this instance for fluent chaining
	 */
	public SendGridV2Builder intercept(SendGridInterceptor interceptor) {
		this.interceptor = interceptor;
		return this;
	}

	@Override
	public SendGridV2Sender build() {
		String apiKey = apiKeyValueBuilder.getValue();
		String username = usernameValueBuilder.getValue();
		String password = passwordValueBuilder.getValue();
		URL url = urlValueBuilder.getValue();
		SendGridClient builtClient = buildClient(apiKey, username, password, url);
		if (builtClient == null) {
			return null;
		}
		LOG.info("Sending email using SendGrid API is registered");
		LOG.debug("SendGrid account: apiKey={}, username={}", apiKey, username);
		return buildContext.register(new SendGridV2Sender(builtClient, buildContentHandler(), interceptor));
	}

	private SendGridClient buildClient(String apiKey, String username, String password, URL url) {
		if (client != null) {
			return client;
		}
		if (apiKey != null || (username != null && password != null)) {
			return buildContext.register(new DelegateSendGridClient(buildSendGrid(apiKey, username, password, url)));
		}
		return null;
	}

	private SendGrid buildSendGrid(String apiKey, String username, String password, URL url) {
		SendGrid sendGrid = newSendGrid(apiKey, username, password);
		if (url != null) {
			sendGrid.setUrl(url.toString());
		}
		if (httpClient != null) {
			sendGrid.setClient(httpClient);
		}
		return sendGrid;
	}

	private SendGrid newSendGrid(String apiKey, String username, String password) {
		if (apiKey != null) {
			return buildContext.register(new SendGrid(apiKey));
		}
		return buildContext.register(new SendGrid(username, password));
	}

	private PriorizedContentHandler buildContentHandler() {
		MimeTypeProvider mimetypeProvider = mimetypeBuilder.build();
		PriorizedContentHandler contentHandler = buildContext.register(new PriorizedContentHandler());
		contentHandler.register(MultiContent.class, buildContext.register(new MultiContentHandler(contentHandler)));
		contentHandler.register(ContentWithAttachments.class, buildContext.register(new ContentWithAttachmentsHandler(contentHandler)));
		contentHandler.register(MayHaveStringContent.class, buildContext.register(new StringContentHandler(mimetypeProvider)));
		return contentHandler;
	}
}