diff options
author | Jesse Morgan <jesse@jesterpm.net> | 2017-09-03 21:44:16 -0700 |
---|---|---|
committer | Jesse Morgan <jesse@jesterpm.net> | 2017-09-03 21:48:53 -0700 |
commit | 72ee0f10ddca0d880e50d13446f9ac0269e542eb (patch) | |
tree | 9dc1bfe0e4300ab05fb3ac1cd44dac6c44b71c18 | |
parent | fa7d0ec7d486dccb55c50ba635a638a855a513c1 (diff) |
Adding notification emails when questions and answers are posted to the feed.20170903
9 files changed, 284 insertions, 8 deletions
@@ -93,6 +93,10 @@ <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-dynamodb</artifactId> </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-ses</artifactId> + </dependency> <dependency> <groupId>io.dropwizard.metrics</groupId> diff --git a/src/main/java/com/p4square/grow/backend/GrowBackend.java b/src/main/java/com/p4square/grow/backend/GrowBackend.java index 4091138..c7b9f42 100644 --- a/src/main/java/com/p4square/grow/backend/GrowBackend.java +++ b/src/main/java/com/p4square/grow/backend/GrowBackend.java @@ -8,6 +8,7 @@ import java.io.IOException; import com.codahale.metrics.MetricRegistry; +import com.p4square.grow.provider.*; import org.apache.log4j.Logger; import org.restlet.Application; @@ -27,12 +28,6 @@ import com.p4square.grow.model.Question; import com.p4square.grow.model.TrainingRecord; import com.p4square.grow.model.UserRecord; -import com.p4square.grow.provider.CollectionProvider; -import com.p4square.grow.provider.Provider; -import com.p4square.grow.provider.ProvidesQuestions; -import com.p4square.grow.provider.ProvidesTrainingRecords; -import com.p4square.grow.provider.ProvidesUserRecords; - import com.p4square.grow.backend.resources.AccountResource; import com.p4square.grow.backend.resources.BannerResource; import com.p4square.grow.backend.resources.SurveyResource; @@ -51,7 +46,7 @@ import com.p4square.restlet.metrics.MetricRouter; * * @author Jesse Morgan <jesse@jesterpm.net> */ -public class GrowBackend extends Application implements GrowData { +public class GrowBackend extends Application implements GrowData, ProvidesNotificationService { private final static Logger LOG = Logger.getLogger(GrowBackend.class); @@ -59,6 +54,7 @@ public class GrowBackend extends Application implements GrowData { private final Config mConfig; private final GrowData mGrowData; + private final NotificationService mNotificationService; public GrowBackend() { this(new Config(), new MetricRegistry()); @@ -70,6 +66,8 @@ public class GrowBackend extends Application implements GrowData { mMetricRegistry = metricRegistry; mGrowData = new DynamoGrowData(config); + + mNotificationService = new SESNotificationService(config); } public MetricRegistry getMetrics() { @@ -179,6 +177,9 @@ public class GrowBackend extends Application implements GrowData { return mGrowData.getAnswerProvider(); } + @Override + public NotificationService getNotificationService() { return mNotificationService; } + /** * Stand-alone main for testing. */ diff --git a/src/main/java/com/p4square/grow/backend/NotificationService.java b/src/main/java/com/p4square/grow/backend/NotificationService.java new file mode 100644 index 0000000..1d87a70 --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/NotificationService.java @@ -0,0 +1,15 @@ +package com.p4square.grow.backend; + +/** + * An implementation of NotificationService sends notifications. + */ +public interface NotificationService { + + /** + * Send a notification from the GROW website to the notification address. + * + * @param message The notification to deliever. + */ + void sendNotification(final String message); + +} diff --git a/src/main/java/com/p4square/grow/backend/SESNotificationService.java b/src/main/java/com/p4square/grow/backend/SESNotificationService.java new file mode 100644 index 0000000..58b732d --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/SESNotificationService.java @@ -0,0 +1,86 @@ +package com.p4square.grow.backend; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.simpleemail.AmazonSimpleEmailService; +import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient; +import com.amazonaws.services.simpleemail.model.*; +import com.p4square.grow.config.Config; +import org.apache.log4j.Logger; + +/** + * Send Notifications via SimpleEmailService. + */ +public class SESNotificationService implements NotificationService { + + private final static Logger LOG = Logger.getLogger(SESNotificationService.class); + + private final AmazonSimpleEmailService mClient; + private final String mSourceAddress; + private final Destination mDestination; + + public SESNotificationService(final Config config) { + AWSCredentials creds; + + String awsAccessKey = config.getString("awsAccessKey"); + if (awsAccessKey != null) { + creds = new AWSCredentials() { + @Override + public String getAWSAccessKeyId() { + return config.getString("awsAccessKey"); + } + @Override + public String getAWSSecretKey() { + return config.getString("awsSecretKey"); + } + }; + } else { + creds = new DefaultAWSCredentialsProviderChain().getCredentials(); + } + + mClient = new AmazonSimpleEmailServiceClient(creds); + + String region = config.getString("awsRegion"); + if (region != null) { + mClient.setRegion(Region.getRegion(Regions.fromName(region))); + } + + mSourceAddress = config.getString("notificationSourceEmail"); + + final String dest = config.getString("notificationEmail"); + if (dest != null) { + mDestination = new Destination().withToAddresses(dest); + } else { + // Notifications are not configured. + mDestination = null; + } + } + + @Override + public void sendNotification(final String message) { + try { + if (mSourceAddress == null || mDestination == null) { + // Disable notifications if there is no source address configured. + LOG.debug("Notifications are disabled because source or destination emails are not configured."); + return; + } + + Message msg = new Message() + .withSubject(new Content().withCharset("UTF-8").withData("Grow Notification")) + .withBody(new Body() + .withText(new Content().withCharset("UTF-8").withData(message))); + + SendEmailRequest request = new SendEmailRequest() + .withDestination(mDestination) + .withSource(mSourceAddress) + .withMessage(msg); + + mClient.sendEmail(request); + + } catch (Exception e) { + LOG.warn("Failed to send notification email", e); + } + } +} diff --git a/src/main/java/com/p4square/grow/backend/feed/ThreadResource.java b/src/main/java/com/p4square/grow/backend/feed/ThreadResource.java index e8f46c2..12ec899 100644 --- a/src/main/java/com/p4square/grow/backend/feed/ThreadResource.java +++ b/src/main/java/com/p4square/grow/backend/feed/ThreadResource.java @@ -9,6 +9,8 @@ import java.io.IOException; import java.util.Date; import java.util.Map; +import com.p4square.grow.backend.NotificationService; +import com.p4square.grow.provider.ProvidesNotificationService; import org.restlet.data.Status; import org.restlet.resource.ServerResource; import org.restlet.representation.Representation; @@ -31,6 +33,8 @@ public class ThreadResource extends ServerResource { private String mTopic; private String mThreadId; + private NotificationService mNotifier; + @Override public void doInit() { super.doInit(); @@ -38,6 +42,8 @@ public class ThreadResource extends ServerResource { mBackend = (FeedDataProvider) getApplication(); mTopic = getAttribute("topic"); mThreadId = getAttribute("thread"); + + mNotifier = ((ProvidesNotificationService) getApplication()).getNotificationService(); } /** @@ -94,6 +100,10 @@ public class ThreadResource extends ServerResource { String collectionKey = mTopic + "/" + mThreadId; mBackend.getMessageProvider().put(collectionKey, message.getId(), message); + // Send a notification email + mNotifier.sendNotification( + String.format("A new response was posted on the %s topic:\n\n%s", mTopic, message.getMessage())); + setLocationRef(mThreadId + "/" + message.getId()); return new JacksonRepresentation(message); diff --git a/src/main/java/com/p4square/grow/backend/feed/TopicResource.java b/src/main/java/com/p4square/grow/backend/feed/TopicResource.java index 24b6a92..914e52c 100644 --- a/src/main/java/com/p4square/grow/backend/feed/TopicResource.java +++ b/src/main/java/com/p4square/grow/backend/feed/TopicResource.java @@ -9,6 +9,8 @@ import java.io.IOException; import java.util.Date; import java.util.Map; +import com.p4square.grow.backend.NotificationService; +import com.p4square.grow.provider.ProvidesNotificationService; import org.restlet.data.Status; import org.restlet.resource.ServerResource; import org.restlet.representation.Representation; @@ -31,12 +33,16 @@ public class TopicResource extends ServerResource { private FeedDataProvider mBackend; private String mTopic; + private NotificationService mNotifier; + @Override public void doInit() { super.doInit(); mBackend = (FeedDataProvider) getApplication(); mTopic = getAttribute("topic"); + + mNotifier = ((ProvidesNotificationService) getApplication()).getNotificationService(); } /** @@ -86,7 +92,7 @@ public class TopicResource extends ServerResource { try { // Deserialize the incoming message. JacksonRepresentation<MessageThread> jsonRep = - new JacksonRepresentation<MessageThread>(entity, MessageThread.class); + new JacksonRepresentation<>(entity, MessageThread.class); // Get the message from the request. // Throw away the wrapping MessageThread because we'll create our own later. @@ -105,6 +111,11 @@ public class TopicResource extends ServerResource { mBackend.getThreadProvider().put(mTopic, newThread.getId(), newThread); + // Send a notification email + mNotifier.sendNotification( + String.format("A new question was posted on the %s topic:\n\n%s", mTopic, message.getMessage())); + + setLocationRef(mTopic + "/" + newThread.getId()); return new JacksonRepresentation(newThread); diff --git a/src/main/java/com/p4square/grow/provider/ProvidesNotificationService.java b/src/main/java/com/p4square/grow/provider/ProvidesNotificationService.java new file mode 100644 index 0000000..c6166ae --- /dev/null +++ b/src/main/java/com/p4square/grow/provider/ProvidesNotificationService.java @@ -0,0 +1,11 @@ +package com.p4square.grow.provider; + +import com.p4square.grow.backend.NotificationService; + +/** + * Indicates that the class can provide a NotificationService instance. + */ +public interface ProvidesNotificationService { + + NotificationService getNotificationService(); +} diff --git a/src/test/java/com/p4square/grow/backend/feed/ThreadResourceTest.java b/src/test/java/com/p4square/grow/backend/feed/ThreadResourceTest.java new file mode 100644 index 0000000..bb4e8ca --- /dev/null +++ b/src/test/java/com/p4square/grow/backend/feed/ThreadResourceTest.java @@ -0,0 +1,68 @@ +package com.p4square.grow.backend.feed; + +import com.p4square.grow.backend.GrowBackend; +import com.p4square.grow.backend.NotificationService; +import com.p4square.grow.model.Message; +import com.p4square.grow.provider.CollectionProvider; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.restlet.Request; +import org.restlet.data.Method; +import org.restlet.ext.jackson.JacksonRepresentation; +import org.restlet.representation.Representation; + +import static org.junit.Assert.assertNotNull; + +/** + * Tests for the feed's ThreadResource. + */ +public class ThreadResourceTest { + + private ThreadResource resource; + + private GrowBackend mockBackend; + private CollectionProvider<String, String, Message> mockProvider; + private NotificationService mockNotificationService; + + @Before + public void setup() { + mockNotificationService = EasyMock.createMock(NotificationService.class); + mockProvider = EasyMock.createMock(CollectionProvider.class); + + mockBackend = EasyMock.createMock(GrowBackend.class); + EasyMock.expect(mockBackend.getMessageProvider()).andReturn(mockProvider).anyTimes(); + EasyMock.expect(mockBackend.getNotificationService()).andReturn(mockNotificationService).anyTimes(); + + resource = new ThreadResource(); + resource.setApplication(mockBackend); + } + + @Test + public void testNotification() throws Exception { + // Prepare request + Message message = new Message(); + message.setMessage("Test message"); + Representation entity = new JacksonRepresentation<>(message); + + Request request = new Request(Method.POST, "/feed/leader/thread-id"); + request.getAttributes().put("topic", "leader"); + request.getAttributes().put("thread", "thread-id"); + + // Set expectations + mockProvider.put(EasyMock.eq("leader/thread-id"), EasyMock.anyString(), EasyMock.anyObject(Message.class)); + mockNotificationService.sendNotification("A new response was posted on the leader topic:\n\nTest message"); + EasyMock.replay(mockBackend, mockProvider, mockNotificationService); + + // Test + resource.setRequest(request); + resource.doInit(); + Representation result = resource.post(entity); + + // Verify + EasyMock.verify(mockBackend, mockProvider, mockNotificationService); + + assertNotNull(result); + } + +}
\ No newline at end of file diff --git a/src/test/java/com/p4square/grow/backend/feed/TopicResourceTest.java b/src/test/java/com/p4square/grow/backend/feed/TopicResourceTest.java new file mode 100644 index 0000000..46f6696 --- /dev/null +++ b/src/test/java/com/p4square/grow/backend/feed/TopicResourceTest.java @@ -0,0 +1,70 @@ +package com.p4square.grow.backend.feed; + +import com.p4square.grow.backend.GrowBackend; +import com.p4square.grow.backend.NotificationService; +import com.p4square.grow.model.Message; +import com.p4square.grow.model.MessageThread; +import com.p4square.grow.provider.CollectionProvider; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.restlet.Request; +import org.restlet.data.Method; +import org.restlet.ext.jackson.JacksonRepresentation; +import org.restlet.representation.Representation; + +import static org.junit.Assert.assertNotNull; + +/** + * Tests for the feed's TopicResource. + */ +public class TopicResourceTest { + + private TopicResource resource; + + private GrowBackend mockBackend; + private CollectionProvider<String, String, MessageThread> mockProvider; + private NotificationService mockNotificationService; + + @Before + public void setup() { + mockNotificationService = EasyMock.createMock(NotificationService.class); + mockProvider = EasyMock.createMock(CollectionProvider.class); + + mockBackend = EasyMock.createMock(GrowBackend.class); + EasyMock.expect(mockBackend.getThreadProvider()).andReturn(mockProvider).anyTimes(); + EasyMock.expect(mockBackend.getNotificationService()).andReturn(mockNotificationService).anyTimes(); + + resource = new TopicResource(); + resource.setApplication(mockBackend); + } + + @Test + public void testNotification() throws Exception { + // Prepare request + Message message = new Message(); + message.setMessage("Test message"); + MessageThread thread = new MessageThread(); + thread.setMessage(message); + Representation entity = new JacksonRepresentation<>(thread); + + Request request = new Request(Method.POST, "/feed/leader"); + request.getAttributes().put("topic", "leader"); + + // Set expectations + mockProvider.put(EasyMock.eq("leader"), EasyMock.anyString(), EasyMock.anyObject(MessageThread.class)); + mockNotificationService.sendNotification("A new question was posted on the leader topic:\n\nTest message"); + EasyMock.replay(mockBackend, mockProvider, mockNotificationService); + + // Test + resource.setRequest(request); + resource.doInit(); + Representation result = resource.post(entity); + + // Verify + EasyMock.verify(mockBackend, mockProvider, mockNotificationService); + + assertNotNull(result); + } + +}
\ No newline at end of file |