diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/notificationservice/BackGroundNotificationActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/notificationservice/BackGroundNotificationActor.java new file mode 100644 index 000000000..5e4f7645a --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/learner/actors/notificationservice/BackGroundNotificationActor.java @@ -0,0 +1,52 @@ +package org.sunbird.learner.actors.notificationservice; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.sunbird.actor.core.BaseActor; +import org.sunbird.actor.router.ActorConfig; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.request.Request; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +@ActorConfig( + tasks = {}, + asyncTasks = {"processNotification"} +) +public class BackGroundNotificationActor extends BaseActor { + @Override + public void onReceive(Request request) throws Throwable { + callNotificationService(request); + } + + private void callNotificationService(Request reqObj) { + Map request = reqObj.getRequest(); + ProjectLogger.log("BackGroundNotificationActor:callNotificationService :: Method called.", LoggerEnum.INFO.name()); + try { + ObjectMapper mapper = new ObjectMapper(); + String notification_service_base_url = System.getenv("notification_service_base_url"); + String NOTIFICATION_SERVICE_URL = notification_service_base_url+"/v1/notification/send"; + ProjectLogger.log("BackGroundNotificationActor:callNotificationService :: calling notification service URL :"+NOTIFICATION_SERVICE_URL,LoggerEnum.INFO.name()); + CloseableHttpClient client = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(NOTIFICATION_SERVICE_URL); + String json = mapper.writeValueAsString(request); + json = new String(json.getBytes(), StandardCharsets.UTF_8); + StringEntity entity = new StringEntity(json); + httpPost.setEntity(entity); + httpPost.setHeader("Accept", "application/json"); + httpPost.setHeader("Content-type", "application/json"); + httpPost.setHeader("requestId", reqObj.getRequestId()); + HttpResponse response = client.execute(httpPost); + ProjectLogger.log("BackGroundNotificationActor:callNotificationService :: Response =" + response.getStatusLine().getStatusCode(), LoggerEnum.INFO.name()); + } catch (Exception ex) { + ProjectLogger.log("BackGroundNotificationActor:callNotificationService :: Error occurred",ex); + } + } +} diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/notificationservice/SendNotificationActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/notificationservice/SendNotificationActor.java new file mode 100644 index 000000000..377b37f96 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/learner/actors/notificationservice/SendNotificationActor.java @@ -0,0 +1,154 @@ +package org.sunbird.learner.actors.notificationservice; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.sunbird.actor.background.BackgroundOperations; +import org.sunbird.actor.core.BaseActor; +import org.sunbird.actor.router.ActorConfig; +import org.sunbird.cassandra.CassandraOperation; +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.models.response.Response; +import org.sunbird.common.models.util.ActorOperations; +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.models.util.datasecurity.DecryptionService; +import org.sunbird.common.request.Request; +import org.sunbird.common.responsecode.ResponseCode; +import org.sunbird.helper.ServiceFactory; +import org.sunbird.learner.actors.notificationservice.dao.EmailTemplateDao; +import org.sunbird.learner.actors.notificationservice.dao.impl.EmailTemplateDaoImpl; +import org.sunbird.learner.util.Util; + +import java.text.MessageFormat; +import java.util.*; + +@ActorConfig( + tasks = {"v2Notification"}, + asyncTasks = {} +) +public class SendNotificationActor extends BaseActor { + @Override + public void onReceive(Request request) throws Throwable { + if (request.getOperation().equalsIgnoreCase(ActorOperations.V2_NOTIFICATION.getValue())) { + sendNotification(request); + } else { + onReceiveUnsupportedOperation(request.getOperation()); + } + } + + private void sendNotification(Request request) { + Map requestMap = + (Map) request.getRequest().get(JsonKey.EMAIL_REQUEST); + List userIds = (List) requestMap.remove(JsonKey.RECIPIENT_USERIDS); + List phoneOrEmailList; + Map notificationReq; + String mode = (String) requestMap.remove(JsonKey.MODE); + ProjectLogger.log("SendNotificationActor:sendNotification : called for mode =" + mode, LoggerEnum.INFO.name()); + if (StringUtils.isNotBlank(mode) + && JsonKey.SMS.equalsIgnoreCase(mode)) { + phoneOrEmailList = getUsersEmailOrPhone(userIds, JsonKey.PHONE); + notificationReq = getNotificationRequest(phoneOrEmailList,requestMap,JsonKey.SMS,null); + } else { + phoneOrEmailList = getUsersEmailOrPhone(userIds, JsonKey.EMAIL); + String template = getEmailTemplateFile((String) requestMap.get(JsonKey.EMAIL_TEMPLATE_TYPE)); + notificationReq = getNotificationRequest(phoneOrEmailList,requestMap,JsonKey.EMAIL,template); + } + ProjectLogger.log("SendNotificationActor:sendNotification : called for userIds =" + userIds, LoggerEnum.INFO.name()); + process(notificationReq,request.getRequestId()); + + Response res = new Response(); + res.put(JsonKey.RESPONSE, JsonKey.SUCCESS); + sender().tell(res, self()); + } + + private void process(Map notificationReq, String requestId) { + List> notificationList = new ArrayList<>(); + notificationList.add(notificationReq); + Map reqMap = new HashMap<>(); + Map request = new HashMap<>(); + request.put("notifications",notificationList); + reqMap.put(JsonKey.REQUEST,request); + + Request bgRequest = new Request(); + bgRequest.setRequestId(requestId); + bgRequest.getRequest().putAll(reqMap); + bgRequest.setOperation("processNotification"); + tellToAnother(bgRequest); + } + + private Map getNotificationRequest(List phoneOrEmailList, Map requestMap, String mode, String template) { + Map notiReq = new HashMap<>(); + notiReq.put("deliveryType","message"); + Map config = new HashMap<>(2); + config.put("sender",System.getenv("sunbird_mail_server_from_email")); + config.put(JsonKey.SUBJECT,requestMap.remove(JsonKey.SUBJECT)); + notiReq.put("config",config); + Map templateMap = new HashMap<>(2); + if (mode.equalsIgnoreCase(JsonKey.SMS)) { + templateMap.put(JsonKey.DATA,requestMap.remove(JsonKey.BODY)); + templateMap.put(JsonKey.PARAMS,Collections.EMPTY_MAP); + notiReq.put("template",templateMap); + notiReq.put(JsonKey.MODE,JsonKey.PHONE); + } else { + templateMap.put(JsonKey.DATA,template); + templateMap.put(JsonKey.PARAMS,requestMap); + notiReq.put("template",templateMap); + notiReq.put(JsonKey.MODE,JsonKey.EMAIL); + } + notiReq.put("ids",phoneOrEmailList); + return notiReq; + } + + private String getEmailTemplateFile(String templateName) { + EmailTemplateDao emailTemplateDao = EmailTemplateDaoImpl.getInstance(); + String template = emailTemplateDao.getTemplate(templateName); + if (StringUtils.isBlank(template)) { + ProjectCommonException.throwClientErrorException( + ResponseCode.invalidParameterValue, + MessageFormat.format( + ResponseCode.invalidParameterValue.getErrorMessage(), + templateName, + JsonKey.EMAIL_TEMPLATE_TYPE)); + } + return template; + } + + private List getUsersEmailOrPhone(List userIds, String key) { + Util.DbInfo usrDbInfo = Util.dbInfoMap.get(JsonKey.USER_DB); + List fields = new ArrayList<>(); + fields.add(key); + Response response = + ServiceFactory.getInstance().getRecordsByIdsWithSpecifiedColumns( + usrDbInfo.getKeySpace(), usrDbInfo.getTableName(), fields, userIds); + List> userList = (List>) response.get(JsonKey.RESPONSE); + return getPhoneOrEmailList(userList, key); + } + + private List getPhoneOrEmailList(List> userList, String key) { + if (CollectionUtils.isNotEmpty(userList)) { + DecryptionService decryptionService = + org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getDecryptionServiceInstance( + null); + List emailOrPhoneList = new ArrayList<>(); + for (Map userMap : userList) { + String email = (String) userMap.get(key); + if (StringUtils.isNotBlank(email)) { + String decryptedEmail = decryptionService.decryptData(email); + emailOrPhoneList.add(decryptedEmail); + } + } + if (CollectionUtils.isNotEmpty(emailOrPhoneList)) { + return emailOrPhoneList; + } else { + ProjectCommonException.throwClientErrorException( + ResponseCode.notificationNotSent, + MessageFormat.format( + ResponseCode.notificationNotSent.getErrorMessage(), + key)); + } + } + return Collections.EMPTY_LIST; + } + +} diff --git a/actors/common/src/test/java/org/sunbird/learner/actors/SendNotificationActorTest.java b/actors/common/src/test/java/org/sunbird/learner/actors/SendNotificationActorTest.java new file mode 100644 index 000000000..b88e19020 --- /dev/null +++ b/actors/common/src/test/java/org/sunbird/learner/actors/SendNotificationActorTest.java @@ -0,0 +1,230 @@ +package org.sunbird.learner.actors; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.dispatch.Futures; +import akka.testkit.javadsl.TestKit; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.sunbird.actor.background.BackgroundOperations; +import org.sunbird.actor.service.SunbirdMWService; +import org.sunbird.cassandraimpl.CassandraOperationImpl; +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.inf.ElasticSearchService; +import org.sunbird.common.models.response.Response; +import org.sunbird.common.models.util.ActorOperations; +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.datasecurity.impl.DefaultDecryptionServiceImpl; +import org.sunbird.common.models.util.datasecurity.impl.DefaultEncryptionServivceImpl; +import org.sunbird.common.request.Request; +import org.sunbird.common.responsecode.ResponseCode; +import org.sunbird.helper.ServiceFactory; +import org.sunbird.learner.actors.notificationservice.SendNotificationActor; +import org.sunbird.learner.actors.notificationservice.dao.impl.EmailTemplateDaoImpl; +import org.sunbird.learner.util.DataCacheHandler; +import org.sunbird.learner.util.Util; +import scala.concurrent.Promise; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static akka.testkit.JavaTestKit.duration; +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ + ServiceFactory.class, + Util.class, + DataCacheHandler.class, + org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.class, + EmailTemplateDaoImpl.class, + SunbirdMWService.class +}) +@PowerMockIgnore({"javax.management.*"}) +public class SendNotificationActorTest { + + private static final Props props = Props.create(SendNotificationActor.class); + private ActorSystem system = ActorSystem.create("system"); + private static CassandraOperationImpl cassandraOperation; + private static DefaultDecryptionServiceImpl defaultDecryptionService; + private static EmailTemplateDaoImpl emailTemplateDao; + private ElasticSearchService esService; + + @BeforeClass + public static void setUp() { + PowerMockito.mockStatic(SunbirdMWService.class); + SunbirdMWService.tellToBGRouter(Mockito.any(), Mockito.any()); + PowerMockito.mockStatic(ServiceFactory.class); + PowerMockito.mockStatic(EmailTemplateDaoImpl.class); + PowerMockito.mockStatic(org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.class); + cassandraOperation = mock(CassandraOperationImpl.class); + defaultDecryptionService = mock(DefaultDecryptionServiceImpl.class); + emailTemplateDao = mock(EmailTemplateDaoImpl.class); + } + + @Before + public void beforeTest() { + + PowerMockito.mockStatic(ServiceFactory.class); + PowerMockito.mockStatic(org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.class); + PowerMockito.mockStatic(EmailTemplateDaoImpl.class); + when(ServiceFactory.getInstance()).thenReturn(cassandraOperation); + when(org.sunbird.common.models.util.datasecurity.impl.ServiceFactory + .getDecryptionServiceInstance(null)) + .thenReturn(defaultDecryptionService); + when(cassandraOperation.getRecordsByIdsWithSpecifiedColumns( + Mockito.anyString(), Mockito.anyString(), Mockito.anyList(), Mockito.anyList())) + .thenReturn(cassandraGetRecordById()); + + emailTemplateDao = mock(EmailTemplateDaoImpl.class); + when(EmailTemplateDaoImpl.getInstance()).thenReturn(emailTemplateDao); + when(emailTemplateDao.getTemplate(Mockito.anyString())).thenReturn("templateName"); + + } + + private static Response cassandraGetRecordById() { + Response response = new Response(); + List> list = new ArrayList(); + Map map = new HashMap<>(); + map.put(JsonKey.ID, "anyId"); + map.put(JsonKey.EMAIL, "anyEmailId"); + list.add(map); + response.put(JsonKey.RESPONSE, list); + return response; + } + + private static Response cassandraGetEmptyRecordById() { + Response response = new Response(); + List> list = new ArrayList(); + Map map = new HashMap<>(); + map.put(JsonKey.ID, "anyId"); + map.put(JsonKey.EMAIL, ""); + list.add(map); + response.put(JsonKey.RESPONSE, list); + return response; + } + + private static Map createGetSkillResponse() { + HashMap response = new HashMap<>(); + List> content = new ArrayList<>(); + HashMap innerMap = new HashMap<>(); + innerMap.put(JsonKey.EMAIL, "anyEmailId"); + content.add(innerMap); + response.put(JsonKey.CONTENT, content); + return response; + } + + @Test + public void testSendEmailSuccess() { + + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request reqObj = new Request(); + reqObj.setOperation(ActorOperations.V2_NOTIFICATION.getValue()); + + HashMap innerMap = new HashMap<>(); + Map reqMap = new HashMap(); + List userIdList = new ArrayList<>(); + userIdList.add("001"); + reqMap.put(JsonKey.RECIPIENT_USERIDS, userIdList); + innerMap.put(JsonKey.EMAIL_REQUEST, reqMap); + reqObj.setRequest(innerMap); + subject.tell(reqObj, probe.getRef()); + Response response = probe.expectMsgClass(duration("10 second"), Response.class); + assertTrue(response != null); + } + + @Test + public void testSendEmailFailureWithInvalidParameterValue() { + PowerMockito.mockStatic(SunbirdMWService.class); + SunbirdMWService.tellToBGRouter(Mockito.any(), Mockito.any()); + when(cassandraOperation.getRecordsByIdsWithSpecifiedColumns( + Mockito.anyString(), Mockito.anyString(), Mockito.anyList(), Mockito.anyList())) + .thenReturn(cassandraGetEmptyRecordById()); + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request reqObj = new Request(); + reqObj.setOperation(ActorOperations.V2_NOTIFICATION.getValue()); + + HashMap innerMap = new HashMap<>(); + Map reqMap = new HashMap(); + List userIdList = new ArrayList<>(); + userIdList.add("001"); + reqMap.put(JsonKey.RECIPIENT_USERIDS, userIdList); + innerMap.put(JsonKey.EMAIL_REQUEST, reqMap); + reqObj.setRequest(innerMap); + + subject.tell(reqObj, probe.getRef()); + ProjectCommonException exc = + probe.expectMsgClass(duration("10 second"), ProjectCommonException.class); + assertTrue(exc.getCode().equals(ResponseCode.notificationNotSent.getErrorCode())); + } + + + @Test + public void testSendEmailFailureWithBlankTemplateName() { + + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request reqObj = new Request(); + reqObj.setOperation(ActorOperations.V2_NOTIFICATION.getValue()); + + HashMap innerMap = new HashMap<>(); + Map reqMap = new HashMap(); + List userIdList = new ArrayList<>(); + userIdList.add("001"); + reqMap.put(JsonKey.RECIPIENT_USERIDS, userIdList); + innerMap.put(JsonKey.EMAIL_REQUEST, reqMap); + reqObj.setRequest(innerMap); + + when(emailTemplateDao.getTemplate(Mockito.anyString())).thenReturn(""); + subject.tell(reqObj, probe.getRef()); + ProjectCommonException exc = + probe.expectMsgClass(duration("10 second"), ProjectCommonException.class); + assertTrue(exc.getCode().equals(ResponseCode.invalidParameterValue.getErrorCode())); + } + + @Test + public void testSendEmailFailureWithInvalidUserIdInList() { + PowerMockito.mockStatic(SunbirdMWService.class); + SunbirdMWService.tellToBGRouter(Mockito.any(), Mockito.any()); + when(cassandraOperation.getRecordsByIdsWithSpecifiedColumns( + Mockito.anyString(), Mockito.anyString(), Mockito.anyList(), Mockito.anyList())) + .thenReturn(cassandraGetEmptyRecordById()); + TestKit probe = new TestKit(system); + ActorRef subject = system.actorOf(props); + Request reqObj = new Request(); + reqObj.setOperation(ActorOperations.V2_NOTIFICATION.getValue()); + + HashMap innerMap = new HashMap<>(); + Map pageMap = new HashMap(); + List emailIdList = new ArrayList<>(); + emailIdList.add("aaa@gmail.com"); + List userIdList = new ArrayList<>(); + userIdList.add("001"); + userIdList.add("002"); + Map userIdMap = new HashMap<>(); + userIdMap.put(JsonKey.RECIPIENT_USERIDS, userIdList); + innerMap.put(JsonKey.EMAIL_REQUEST, userIdMap); + + reqObj.setRequest(innerMap); + subject.tell(reqObj, probe.getRef()); + ProjectCommonException exc = + probe.expectMsgClass(duration("10 second"), ProjectCommonException.class); + assertTrue(exc.getCode().equals(ResponseCode.notificationNotSent.getErrorCode())); + } + + +} diff --git a/service/src/main/resources/application.conf b/service/src/main/resources/application.conf index 4a5149274..aef34274e 100644 --- a/service/src/main/resources/application.conf +++ b/service/src/main/resources/application.conf @@ -138,6 +138,18 @@ SunbirdMWSystem { nr-of-instances = 2 dispatcher = brr-usr-dispatcher } + "/RequestRouter/*/SendNotificationActor" + { + router = smallest-mailbox-pool + nr-of-instances = 15 + dispatcher = page-mgr-actor-dispatcher + } + "/BackgroundRequestRouter/*/BackGroundNotificationActor" + { + router = smallest-mailbox-pool + nr-of-instances = 15 + dispatcher = page-mgr-actor-dispatcher + } "/RequestRouter/*/EmailServiceActor" { router = smallest-mailbox-pool