Refactor common email sending utility

The main thrust of this is to create a common POJO that contains email content in a simple way, then have one class that converts that to an email and sends it. Any class that uses email should only have to deal with creating that POJO.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=237883643
This commit is contained in:
gbrodman 2019-03-11 14:30:41 -07:00 committed by Ben McIlwain
parent 9823ee7fcf
commit 50e0a9b532
35 changed files with 773 additions and 614 deletions

View file

@ -11,6 +11,7 @@ java_library(
"//third_party/jaxb",
"//third_party/objectify:objectify-v4_1",
"@com_google_appengine_api_1_0_sdk",
"@com_google_auto_value",
"@com_google_code_findbugs_jsr305",
"@com_google_dagger",
"@com_google_errorprone_error_prone_annotations",

View file

@ -0,0 +1,94 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.util;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.net.MediaType;
import java.util.Collection;
import java.util.Optional;
import javax.mail.internet.InternetAddress;
/** Value class representing the content and metadata of an email. */
@AutoValue
public abstract class EmailMessage {
public abstract String subject();
public abstract String body();
public abstract ImmutableList<InternetAddress> recipients();
public abstract InternetAddress from();
public abstract Optional<InternetAddress> bcc();
public abstract Optional<MediaType> contentType();
public abstract Optional<Attachment> attachment();
public static Builder newBuilder() {
return new AutoValue_EmailMessage.Builder();
}
public static EmailMessage create(
String subject, String body, InternetAddress recipient, InternetAddress from) {
return newBuilder()
.setSubject(subject)
.setBody(body)
.setRecipients(ImmutableList.of(recipient))
.setFrom(from)
.build();
}
/** Builder for {@link EmailMessage}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setSubject(String subject);
public abstract Builder setBody(String body);
public abstract Builder setRecipients(Collection<InternetAddress> recipients);
public abstract Builder setFrom(InternetAddress from);
public abstract Builder setBcc(InternetAddress bcc);
public abstract Builder setContentType(MediaType contentType);
public abstract Builder setAttachment(Attachment attachment);
abstract ImmutableList.Builder<InternetAddress> recipientsBuilder();
public Builder addRecipient(InternetAddress value) {
recipientsBuilder().add(value);
return this;
}
public abstract EmailMessage build();
}
/** An attachment to the email, if one exists. */
@AutoValue
public abstract static class Attachment {
public abstract MediaType contentType();
public abstract String filename();
public abstract String content();
public static Builder newBuilder() {
return new AutoValue_EmailMessage_Attachment.Builder();
}
/** Builder for {@link Attachment}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setContentType(MediaType contentType);
public abstract Builder setFilename(String filename);
public abstract Builder setContent(String content);
public abstract Attachment build();
}
}
}

View file

@ -14,29 +14,78 @@
package google.registry.util;
import static com.google.common.collect.Iterables.toArray;
import com.google.common.net.MediaType;
import google.registry.util.EmailMessage.Attachment;
import java.io.IOException;
import java.util.Properties;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
/** Wrapper around javax.mail's Transport.send that can be mocked for testing purposes. */
/**
* Wrapper around javax.mail's email creation and sending functionality. Encompasses a retry policy
* as well.
*/
@Singleton
public class SendEmailService {
@Inject
SendEmailService() {};
private final Retrier retrier;
private final TransportEmailSender transportEmailSender;
/** Returns a new MimeMessage using default App Engine transport settings. */
public Message createMessage() {
return new MimeMessage(Session.getDefaultInstance(new Properties(), null));
@Inject
SendEmailService(Retrier retrier, TransportEmailSender transportEmailSender) {
this.retrier = retrier;
this.transportEmailSender = transportEmailSender;
}
/** Sends a message using default App Engine transport. */
public void sendMessage(Message msg) throws MessagingException {
Transport.send(msg);
/**
* Converts the provided message content into a {@link javax.mail.Message} and sends it with
* retry on transient failures.
*/
public void sendEmail(EmailMessage emailMessage) {
retrier.callWithRetry(
() -> {
Message msg =
new MimeMessage(
Session.getDefaultInstance(new Properties(), /* authenticator= */ null));
msg.setFrom(emailMessage.from());
msg.addRecipients(
RecipientType.TO, toArray(emailMessage.recipients(), InternetAddress.class));
msg.setSubject(emailMessage.subject());
Multipart multipart = new MimeMultipart();
BodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(
emailMessage.body(),
emailMessage.contentType().orElse(MediaType.PLAIN_TEXT_UTF_8).toString());
multipart.addBodyPart(bodyPart);
if (emailMessage.attachment().isPresent()) {
Attachment attachment = emailMessage.attachment().get();
BodyPart attachmentPart = new MimeBodyPart();
attachmentPart.setContent(attachment.content(), attachment.contentType().toString());
attachmentPart.setFileName(attachment.filename());
multipart.addBodyPart(attachmentPart);
}
if (emailMessage.bcc().isPresent()) {
msg.addRecipient(RecipientType.BCC, emailMessage.bcc().get());
}
msg.setContent(multipart);
msg.saveChanges();
transportEmailSender.sendMessage(msg);
},
IOException.class,
MessagingException.class);
}
}

View file

@ -0,0 +1,34 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.util;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Transport;
/** Wrapper for sending email so that we can test {@link google.registry.util.SendEmailService}. */
@Singleton
class TransportEmailSender {
@Inject
TransportEmailSender() {}
/** Sends a message using default App Engine transport. */
void sendMessage(Message msg) throws MessagingException {
Transport.send(msg);
}
}