All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threewks.thundr.gmail.GmailMailer Maven / Gradle / Ivy

The newest version!
/*
 * This file is a component of thundr, a software library from 3wks.
 * Read more: http://3wks.github.io/thundr/
 * Copyright (C) 2015 3wks, 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.threewks.thundr.gmail;

import com.atomicleopard.expressive.ETransformer;
import com.atomicleopard.expressive.Expressive;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.util.Base64;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.model.Draft;
import com.google.api.services.gmail.model.Message;
import com.threewks.thundr.exception.BaseException;
import com.threewks.thundr.logger.Logger;
import com.threewks.thundr.mail.BaseMailer;
import com.threewks.thundr.mail.Mailer;
import com.threewks.thundr.request.InMemoryResponse;
import com.threewks.thundr.request.RequestContainer;
import com.threewks.thundr.view.ViewResolverRegistry;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class GmailMailer extends BaseMailer implements Mailer {

	public static final String CREDENTIAL_USER_ID = "gmail-credentials";

	private final GoogleAuthorizationCodeFlow flow;

	public GmailMailer(ViewResolverRegistry viewResolverRegistry, GoogleAuthorizationCodeFlow gmailAuthorizationCodeFlow, RequestContainer requestContainer) {
		super(viewResolverRegistry, requestContainer);
		this.flow = gmailAuthorizationCodeFlow;
	}

	/**
	 * @param credentialId is the id to save the {@link com.google.api.client.auth.oauth2.StoredCredential} in datastore used by oauth process
	 *                     You can use any unique id to associate your gmail account.
	 *
	 * @return gmail instance for the given credentialId
	 */
	protected Gmail getClient(String credentialId) {
		try {
			Logger.info("Loading StoredCredential id %s", credentialId);
			return new Gmail.Builder(flow.getTransport(), flow.getJsonFactory(), flow.loadCredential(credentialId)).build();
		} catch (IOException e) {
			String message = String.format("Error loading stored credential for inbox %s: %s", credentialId, e.getMessage());
			Logger.error(message);
			throw new BaseException(e, message);
		}
	}

	/**
	 *
	 * @return gmail instance. Saves the {@link com.google.api.client.auth.oauth2.StoredCredential}	 with id CREDENTIAL_USER_ID
	 **/
	protected Gmail getClient() {
		return getClient(CREDENTIAL_USER_ID);
	}


	@Override
	protected void sendInternal(Map.Entry from, Map.Entry replyTo, Map to, Map cc, Map bcc, String subject, Object body, List attachments) {
		sendGmailInternal(from, replyTo, to, cc, bcc, subject, body, attachments);
	}

	@Deprecated
	protected void sendGmailInternal(Map.Entry from, Map.Entry replyTo, Map to, Map cc, Map bcc, String subject, Object body, List attachments) {
		String content = render(body).getBodyAsString();

		InternetAddress fromAddress = Transformers.FormatInternetAddress.from(from);
		InternetAddress replyToAddress = null;

		Set toAddresses = null;
		Set ccAddresses = null;
		Set bccAddresses = null;

		if (Expressive.isNotEmpty(to)) {
			toAddresses = getInternetAddresses(to);
		}
		if (Expressive.isNotEmpty(cc)) {
			ccAddresses = getInternetAddresses(cc);
		}
		if (Expressive.isNotEmpty(bcc)) {
			bccAddresses = getInternetAddresses(bcc);
		}
		if (replyTo != null) {
			replyToAddress = Transformers.FormatInternetAddress.from(replyTo);
		}

		MimeMessage mimeMessage = createEmailWithAttachment(toAddresses, fromAddress, ccAddresses, bccAddresses,
				replyToAddress, subject, content, attachments);

		Message message = createMessageWithEmail(mimeMessage);
		try {
			getClient().users().messages().send("me", message).execute();
		} catch (IOException e) {
			Logger.error("Failed to send email: %s", e.getMessage());
			throw new GmailException(e);
		}
	}

	private Set getInternetAddresses(Map addressStrings) {
		Set addresses = new LinkedHashSet<>();
		for (Map.Entry toAddrStr : addressStrings.entrySet()) {
			addresses.add(Transformers.FormatInternetAddress.from(toAddrStr));
		}
		return addresses;
	}

	/**
	 * Create a MimeMessage using the parameters provided.
	 *
	 * @param to       Email address of the receiver.
	 * @param from     Email address of the sender, the mailbox account.
	 * @param subject  Subject of the email.
	 * @param bodyText Body text of the email.
	 * @return MimeMessage to be used to send email.
	 */
	protected MimeMessage createEmailWithAttachment(Set to, InternetAddress from, Set cc,
			Set bcc, InternetAddress replyTo, String subject,
			String bodyText, List attachments) {
		Properties props = new Properties();
		Session session = Session.getDefaultInstance(props, null);

		MimeMessage email = new MimeMessage(session);
		try {

			email.setFrom(from);

			if (to != null) {
				email.addRecipients(javax.mail.Message.RecipientType.TO, to.toArray(new InternetAddress[to.size()]));
			}
			if (cc != null) {
				email.addRecipients(javax.mail.Message.RecipientType.CC, cc.toArray(new InternetAddress[cc.size()]));
			}
			if (bcc != null) {
				email.addRecipients(javax.mail.Message.RecipientType.BCC, bcc.toArray(new InternetAddress[bcc.size()]));
			}
			if (replyTo != null) {
				email.setReplyTo(new Address[] { replyTo });
			}

			email.setSubject(subject);

			MimeBodyPart mimeBodyPart = new MimeBodyPart();
			mimeBodyPart.setContent(bodyText, "text/html");
			mimeBodyPart.setHeader("Content-Type", "text/html; charset=\"UTF-8\"");

			Multipart multipart = new MimeMultipart();
			multipart.addBodyPart(mimeBodyPart);

			if (attachments != null) {
				for (com.threewks.thundr.mail.Attachment attachment : attachments) {
					mimeBodyPart = new MimeBodyPart();

					InMemoryResponse renderedResult = render(attachment.view());

					byte[] data = renderedResult.getBodyAsBytes();
					String attachmentContentType = renderedResult.getContentTypeString();
					String attachmentCharacterEncoding = renderedResult.getCharacterEncoding();

					populateMimeBodyPart(mimeBodyPart, attachment, data, attachmentContentType, attachmentCharacterEncoding);

					multipart.addBodyPart(mimeBodyPart);
				}
			}

			email.setContent(multipart);
		} catch (MessagingException e) {
			Logger.error(e.getMessage());
			Logger.error("Failed to create email from: %s;%s, to: %s, cc: %s, bcc: %s, replyTo %s;%s, subject %s, body %s, number of attachments %d", from.getAddress(), from.getPersonal(),
					Transformers.InternetAddressesToString.from(to), Transformers.InternetAddressesToString.from(cc), Transformers.InternetAddressesToString.from(bcc),
					replyTo == null ? "null" : replyTo.getAddress(), replyTo == null ? "null" : replyTo.getPersonal(), subject, bodyText, attachments == null ? 0 : attachments.size());
			throw new GmailException(e);
		}

		return email;
	}

	protected void populateMimeBodyPart(MimeBodyPart mimeBodyPart, com.threewks.thundr.mail.Attachment attachment, byte[] data, String attachmentContentType, String attachmentCharacterEncoding) throws MessagingException {
		String fullContentType = attachmentContentType + "; charset=" + attachmentCharacterEncoding;
		mimeBodyPart.setFileName(attachment.name());
		mimeBodyPart.setContent(data, fullContentType);
		mimeBodyPart.setDisposition(attachment.disposition().toString());
		if (attachment.isInline()) {
			mimeBodyPart.setContentID("<" + attachment.name() + ">");
		}
	}

	/**
	 * Create a Message from an email
	 *
	 * @param email Email to be set to raw of message
	 * @return Message containing base64url encoded email.
	 */
	protected Message createMessageWithEmail(MimeMessage email) {
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		try {
			email.writeTo(bytes);
		} catch (MessagingException | IOException e) {
			Logger.error("Could not write email to output stream: %s", e.getMessage());
			throw new GmailException(e);
		}
		String encodedEmail = Base64.encodeBase64URLSafeString(bytes.toByteArray());
		Message message = new Message();
		message.setRaw(encodedEmail);
		return message;
	}

	/**
	 * Sends a draft email to the authrorised application linked to {@link com.google.api.client.auth.oauth2.StoredCredential} id/name credentialId
	 * in datastore.
	 * @param body the message body
	 * @param subject the message subject
	 * @param toAddress a map of recipients
	 * @param attachments a list of attachments
	 * @param credentialId the id/name used to store the {@link com.google.api.client.auth.oauth2.StoredCredential} in datastore
	 */
	public void createDraft(String body, String subject, Map toAddress, List attachments, String credentialId) {
		try {
			MimeMessage email = createMime(body, subject, toAddress, attachments);
			Message message = createMessageWithEmail(email);
			Draft draft = new Draft();
			draft.setMessage(message);
			Gmail gmail = getClient(credentialId);
			Logger.info("created gmail client %s", gmail);
			gmail.users().drafts().create("me", draft).execute();
			Logger.info("Draft email sent");
		} catch (Exception e) {
			String message = String.format("Error creating draft with body[%s], subject[%s], credentialId[%s]", body, subject, credentialId);
			Logger.error(message);
			throw new GmailException(e);
		}
	}

	private MimeMessage createMime(String bodyText, String subject, Map to, List pdfs) throws MessagingException {
		Properties props = new Properties();
		Session session = Session.getDefaultInstance(props, null);
		MimeMessage email = new MimeMessage(session);
		Set toAddresses = getInternetAddresses(to);

		if (!toAddresses.isEmpty()) {
			email.addRecipients(javax.mail.Message.RecipientType.TO, toAddresses.toArray(new InternetAddress[to.size()]));
		}

		email.setSubject(subject);

		MimeBodyPart mimeBodyPart = new MimeBodyPart();
		mimeBodyPart.setContent(bodyText, "text/html");
		mimeBodyPart.setHeader("Content-Type", "text/html; charset=\"UTF-8\"");

		Multipart multipart = new MimeMultipart();
		for (Attachment attachmentPdf : pdfs) {
			MimeBodyPart attachment = new MimeBodyPart();
			DataSource source = new ByteArrayDataSource(attachmentPdf.getData(), "application/pdf");
			attachment.setDataHandler(new DataHandler(source));
			attachment.setFileName(attachmentPdf.getFileName());
			multipart.addBodyPart(mimeBodyPart);
			multipart.addBodyPart(attachment);
		}
		email.setContent(multipart);
		return email;
	}


	public static class Transformers {

		public static final ETransformer, InternetAddress> FormatInternetAddress = new ETransformer, InternetAddress>() {
			@Override
			public InternetAddress from(Map.Entry address) {
				try {
					return new InternetAddress(address.getKey(), address.getValue());
				} catch (UnsupportedEncodingException e) {
					Logger.error(e.getMessage());
					Logger.error("Error converting email address %s, %s to an Internet Address", address.getKey(), address.getValue());
					throw new GmailException(e);
				}
			}
		};
		public static final ETransformer, String> InternetAddressesToString = new ETransformer, String>() {
			@Override
			public String from(Set internetAddresses) {
				StringBuilder builder = new StringBuilder();
				for (InternetAddress address : internetAddresses) {
					builder.append(address.getAddress())
							.append(",")
							.append(address.getPersonal());
				}
				return builder.toString();
			}
		};
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy