This is an automated email from the ASF dual-hosted git repository. ikamga pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract-cn-notifications.git
commit 13a1f550593b36b72714433d45e431781e83e7eb Author: Ebenezer Graham <[email protected]> AuthorDate: Fri May 24 16:46:20 2019 +0400 Message Templating --- .../client/ConfigurationAlreadyExistException.java | 14 +- .../api/v1/client/NotificationManager.java | 11 +- .../v1/client/TemplateAlreadyExistException.java | 14 +- .../api/v1/client/TemplateNotFoundException.java | 14 +- .../cn/notification/api/v1/domain/Template.java | 110 +++++ .../api/v1/events/NotificationEventConstants.java | 3 + component-test/build.gradle | 4 +- .../cn/notification/AbstractNotificationTest.java | 11 +- .../fineract/cn/notification/TestEmailService.java | 88 ++-- .../fineract/cn/notification/TestSMSService.java | 4 +- .../apache/fineract/cn/notification/TestSuite.java | 5 +- .../notification/importer/TestTemplateImport.java | 57 +++ .../main/resources/importdata/test-templates.csv | 20 + component-test/src/main/resources/logback-test.xml | 35 ++ .../src/main/resources/templates/template.html | 457 +++++++++++++++++++++ service/build.gradle | 5 +- .../CreateTemplateCommand.java} | 34 +- ...andHandler.java => TemplateCommandHandler.java} | 56 +-- .../internal/config/NotificationConfiguration.java | 33 +- .../internal/importer/TemplateImporter.java | 92 +++++ .../internal/mapper/EmailConfigurationMapper.java | 4 + .../service/internal/mapper/TemplateMapper.java | 51 +++ .../internal/repository/ApplicationEntity.java | 106 ----- .../EmailGatewayConfigurationRepository.java | 4 +- .../SMSGatewayConfigurationRepository.java | 4 +- .../internal/repository/TemplateEntity.java | 56 ++- .../internal/repository/TemplateRepository.java | 8 +- .../service/internal/service/EmailService.java | 138 +++---- .../internal/service/NotificationService.java | 34 +- .../service/internal/service/SMSService.java | 18 +- .../service/internal/service/TemplateService.java | 60 +++ .../service/internal/service/util/MailBuilder.java | 36 +- .../service/listener/CustomerEventListener.java | 89 ++-- .../service/rest/EmailServiceRestController.java | 4 +- .../service/rest/SMSServiceRestController.java | 4 +- ...Controller.java => TemplateRestController.java} | 58 ++- service/src/main/resources/application.yml | 4 + .../db/migrations/mariadb/V1__initial_setup.sql | 24 +- .../main/resources/templatedetails/templates.csv | 28 ++ service/src/main/resources/templates/template.html | 457 +++++++++++++++++++++ shared.gradle | 4 +- 41 files changed, 1784 insertions(+), 474 deletions(-) diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/ConfigurationAlreadyExistException.java similarity index 71% copy from component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java copy to api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/ConfigurationAlreadyExistException.java index ad4208a..cf06584 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java +++ b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/ConfigurationAlreadyExistException.java @@ -16,17 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cn.notification; +package org.apache.fineract.cn.notification.api.v1.client; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) [email protected]({ - TestEmailService.class, - TestSMSService.class, - EmailApiDocumentation.class, - SmsApiDocumentation.class, -}) -public class TestSuite extends SuiteTestEnvironment { +public final class ConfigurationAlreadyExistException extends RuntimeException { } diff --git a/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/NotificationManager.java b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/NotificationManager.java index 8b6c76c..992a5bb 100644 --- a/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/NotificationManager.java +++ b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/NotificationManager.java @@ -23,6 +23,7 @@ import org.apache.fineract.cn.api.annotation.ThrowsExceptions; import org.apache.fineract.cn.api.util.CustomFeignClientsConfiguration; import org.apache.fineract.cn.notification.api.v1.domain.EmailConfiguration; import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; +import org.apache.fineract.cn.notification.api.v1.domain.Template; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -68,6 +69,14 @@ public interface NotificationManager { String createEmailConfiguration(final EmailConfiguration emailConfiguration); @RequestMapping( + value = "/template/create", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE + ) + String createTemplate(final Template template); + + @RequestMapping( value = "/configuration/sms/{identifier}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE, @@ -111,4 +120,4 @@ public interface NotificationManager { ) void deleteEmailConfiguration(@PathVariable("identifier") final String identifier); -} \ No newline at end of file +} diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/TemplateAlreadyExistException.java similarity index 71% copy from component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java copy to api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/TemplateAlreadyExistException.java index ad4208a..75de8db 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java +++ b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/TemplateAlreadyExistException.java @@ -16,17 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cn.notification; +package org.apache.fineract.cn.notification.api.v1.client; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) [email protected]({ - TestEmailService.class, - TestSMSService.class, - EmailApiDocumentation.class, - SmsApiDocumentation.class, -}) -public class TestSuite extends SuiteTestEnvironment { +public final class TemplateAlreadyExistException extends RuntimeException { } diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/TemplateNotFoundException.java similarity index 71% copy from component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java copy to api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/TemplateNotFoundException.java index ad4208a..6d5b09e 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java +++ b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/client/TemplateNotFoundException.java @@ -16,17 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cn.notification; +package org.apache.fineract.cn.notification.api.v1.client; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) [email protected]({ - TestEmailService.class, - TestSMSService.class, - EmailApiDocumentation.class, - SmsApiDocumentation.class, -}) -public class TestSuite extends SuiteTestEnvironment { +public final class TemplateNotFoundException extends RuntimeException { } diff --git a/api/src/main/java/org/apache/fineract/cn/notification/api/v1/domain/Template.java b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/domain/Template.java new file mode 100644 index 0000000..10ab584 --- /dev/null +++ b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/domain/Template.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.cn.notification.api.v1.domain; +/* +ebenezergraham created on 5/22/19 +*/ + +import java.util.Objects; + +public class Template { + private String templateIdentifier; + private String subject; + private String senderEmail; + private String message; + private String url; + + public Template(String templateIdentifier, String senderEmail, String subject, String message, String url) { + this.templateIdentifier = templateIdentifier; + this.senderEmail = senderEmail; + this.subject = subject; + this.message = message; + this.url = url; + } + + public Template() { + } + + public String getTemplateIdentifier() { + return templateIdentifier; + } + + public void setTemplateIdentifier(String templateIdentifier) { + this.templateIdentifier = templateIdentifier; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getSenderEmail() { + return senderEmail; + } + + public void setSenderEmail(String senderEmail) { + this.senderEmail = senderEmail; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String toString() { + return "Template{" + + "templateIdentifier='" + templateIdentifier + '\'' + + ", subject='" + subject + '\'' + + ", senderEmail='" + senderEmail + '\'' + + ", message='" + message + '\'' + + ", url='" + url + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Template template = (Template) o; + return Objects.equals(templateIdentifier, template.templateIdentifier) && + Objects.equals(subject, template.subject) && + Objects.equals(message, template.message) && + Objects.equals(url, template.url); + } + + @Override + public int hashCode() { + return Objects.hash(templateIdentifier, subject, message, senderEmail,url); + } +} diff --git a/api/src/main/java/org/apache/fineract/cn/notification/api/v1/events/NotificationEventConstants.java b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/events/NotificationEventConstants.java index b6855b1..ef54997 100644 --- a/api/src/main/java/org/apache/fineract/cn/notification/api/v1/events/NotificationEventConstants.java +++ b/api/src/main/java/org/apache/fineract/cn/notification/api/v1/events/NotificationEventConstants.java @@ -27,11 +27,13 @@ public interface NotificationEventConstants { String POST_SMS_CONFIGURATION = "post-sms-configuration"; String POST_EMAIL_CONFIGURATION = "post-email-configuration"; + String POST_TEMPLATE = "post-template"; String POST_SOURCE_APPLICATION = "post-source-application"; String UPDATE_SMS_CONFIGURATION = "update-sms-configuration"; String UPDATE_EMAIL_CONFIGURATION = "update-email-configuration"; String DELETE_SMS_CONFIGURATION = "delete-sms-configuration"; String DELETE_EMAIL_CONFIGURATION = "delete-email-configuration"; + String DELETE_TEMPLATE = "delete-template"; String DELETE_SOURCE_APPLICATION = "delete-source-application"; String POST_SEND_EMAIL_NOTIFICATION = "post-send-email-notification"; @@ -47,6 +49,7 @@ public interface NotificationEventConstants { String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'"; String SELECTOR_POST_SMS_CONFIGURATION = SELECTOR_NAME + " = '" + POST_SMS_CONFIGURATION + "'"; String SELECTOR_POST_EMAIL_CONFIGURATION = SELECTOR_NAME + " = '" + POST_EMAIL_CONFIGURATION + "'"; + String SELECTOR_POST_TEMPLATE = SELECTOR_NAME + " = '" + POST_TEMPLATE + "'"; String SELECTOR_UPDATE_SMS_CONFIGURATION = SELECTOR_NAME + " = '" + UPDATE_SMS_CONFIGURATION + "'"; String SELECTOR_UPDATE_EMAIL_CONFIGURATION = SELECTOR_NAME + " = '" + UPDATE_EMAIL_CONFIGURATION + "'"; String SELECTOR_DELETE_SMS_CONFIGURATION = SELECTOR_NAME + " = '" + DELETE_SMS_CONFIGURATION + "'"; diff --git a/component-test/build.gradle b/component-test/build.gradle index c0aaace..f0c2f41 100644 --- a/component-test/build.gradle +++ b/component-test/build.gradle @@ -49,7 +49,9 @@ dependencies { [group: 'org.apache.fineract.cn', name: 'lang', version: versions.frameworklang], [group: 'org.springframework.boot', name: 'spring-boot-starter-test'], [group: 'org.springframework.restdocs', name: 'spring-restdocs-mockmvc'], - [group: 'junit', name: 'junit', version: versions.junit] + [group: 'junit', name: 'junit', version: versions.junit], + [group: 'org.apache.commons', name: 'commons-csv', version: versions.apachecsvreader] + ) } asciidoctor { diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/AbstractNotificationTest.java b/component-test/src/main/java/org/apache/fineract/cn/notification/AbstractNotificationTest.java index d80eef2..1f9c45a 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/AbstractNotificationTest.java +++ b/component-test/src/main/java/org/apache/fineract/cn/notification/AbstractNotificationTest.java @@ -20,8 +20,10 @@ package org.apache.fineract.cn.notification; import org.apache.fineract.cn.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule; import org.apache.fineract.cn.api.context.AutoUserContext; +import org.apache.fineract.cn.notification.api.v1.client.NotificationManager; import org.apache.fineract.cn.notification.api.v1.events.NotificationEventConstants; import org.apache.fineract.cn.notification.service.internal.config.NotificationConfiguration; +import org.apache.fineract.cn.notification.service.internal.service.NotificationService; import org.apache.fineract.cn.test.fixture.TenantDataStoreContextTestRule; import org.apache.fineract.cn.test.listener.EnableEventRecording; import org.apache.fineract.cn.test.listener.EventRecorder; @@ -51,14 +53,21 @@ public class AbstractNotificationTest extends SuiteTestEnvironment { public final static TenantDataStoreContextTestRule tenantDataStoreContext = TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer); public static final String LOGGER_NAME = "test-logger"; public static final String TEST_USER = "homer"; + public static final String TEST_ADDRESS = "[email protected]"; + public static final String TEST_TEMPLATE= "test_sample"; @SuppressWarnings("WeakerAccess") @Autowired @Qualifier(LOGGER_NAME) - Logger logger; + public Logger logger; public AutoUserContext userContext; @Autowired public EventRecorder eventRecorder; + @Autowired + public NotificationManager testSubject; + @Autowired + public NotificationService notificationService; + @Rule public final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize); diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/TestEmailService.java b/component-test/src/main/java/org/apache/fineract/cn/notification/TestEmailService.java index 6808678..702e437 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/TestEmailService.java +++ b/component-test/src/main/java/org/apache/fineract/cn/notification/TestEmailService.java @@ -20,29 +20,29 @@ package org.apache.fineract.cn.notification; import org.apache.commons.lang3.RandomStringUtils; import org.apache.fineract.cn.api.util.NotFoundException; -import org.apache.fineract.cn.customer.api.v1.client.CustomerNotFoundException; +import org.apache.fineract.cn.customer.api.v1.domain.Address; +import org.apache.fineract.cn.customer.api.v1.domain.Customer; import org.apache.fineract.cn.notification.api.v1.client.ConfigurationNotFoundException; -import org.apache.fineract.cn.notification.api.v1.client.NotificationManager; import org.apache.fineract.cn.notification.api.v1.domain.EmailConfiguration; import org.apache.fineract.cn.notification.api.v1.events.NotificationEventConstants; +import org.apache.fineract.cn.notification.service.internal.importer.TemplateImporter; import org.apache.fineract.cn.notification.service.internal.service.EmailService; -import org.apache.fineract.cn.notification.service.internal.service.EventHelper; -import org.apache.fineract.cn.notification.service.internal.service.NotificationService; import org.apache.fineract.cn.notification.util.DomainObjectGenerator; -import org.apache.fineract.cn.test.listener.EventRecorder; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + public class TestEmailService extends AbstractNotificationTest { final EmailConfiguration emailConfiguration; @Autowired - private NotificationService notificationService; - @Autowired private EmailService emailService; - @Autowired - private NotificationManager notificationManager; + public TestEmailService() { super(); @@ -51,28 +51,62 @@ public class TestEmailService extends AbstractNotificationTest { @Test - public void shouldSendAnEmail() throws InterruptedException { + public void shouldSendAnEmail() throws InterruptedException, IOException { this.logger.info("Send Email Notification"); - String messageHash = notificationService.sendEmail("[email protected]", - "[email protected]", - "Address Details Changed", - "Dear Valued Customer," + - "\n\nYour address has been changed successfully" + - "\nStreet: Test Street" + - "\nCity: Test City" + - "\nState: Test State" + - "\nCountry: Mauritius" + - "\n\nBest Regards" + - "\nMFI"); + final TemplateImporter importer = new TemplateImporter(testSubject, logger); + final URL uri = ClassLoader.getSystemResource("importdata/test-templates.csv"); + importer.importCSV(uri); +// Assert.assertTrue(eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, TEST_TEMPLATE)); + eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, TEST_TEMPLATE); + + notificationService.sendEmail( + TEST_ADDRESS, + TEST_TEMPLATE, + null); + //Assert.assertTrue(this.eventRecorder.wait(NotificationEventConstants.POST_SEND_EMAIL_NOTIFICATION,TEST_ADDRESS )); + this.eventRecorder.wait(NotificationEventConstants.POST_SEND_EMAIL_NOTIFICATION,TEST_ADDRESS ); + } + + @Test + public void shouldSendFormattedEmail() throws InterruptedException, IOException { + this.logger.info("Send Email Notification"); + final TemplateImporter importer = new TemplateImporter(testSubject, logger); + final URL uri = ClassLoader.getSystemResource("importdata/test-templates.csv"); + importer.importCSV(uri); + + //Assert.assertTrue(eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, TEST_TEMPLATE)); + eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, TEST_TEMPLATE); + + Customer customerPayload = new Customer(); + customerPayload.setGivenName("Test"); + customerPayload.setSurname("User"); + Address address = new Address(); + address.setCity("Cape Coast"); + address.setCity("Street"); + address.setCountry("Ghana"); + address.setCountryCode("GH"); + address.setRegion("Central Region"); + address.setPostalCode("T22022"); + customerPayload.setAddress(address); + + Map<String, Object> templateVariables = new HashMap<>(); + templateVariables.put(customerPayload.getClass().getName().toLowerCase(),customerPayload); + + notificationService.sendFormattedEmail( + TEST_ADDRESS, + TEST_TEMPLATE, + templateVariables + ); - Assert.assertNotNull(messageHash); + //Assert.assertTrue(this.eventRecorder.wait(NotificationEventConstants.POST_SEND_EMAIL_NOTIFICATION,TEST_ADDRESS )); + this.eventRecorder.wait(NotificationEventConstants.POST_SEND_EMAIL_NOTIFICATION,TEST_ADDRESS ); } @Test(expected = NotFoundException.class) public void emailConfigurationNotFound() throws ConfigurationNotFoundException { logger.info("Configuration not found"); try { - this.notificationManager.findEmailConfigurationByIdentifier(RandomStringUtils.randomAlphanumeric(8)); + this.testSubject.findEmailConfigurationByIdentifier(RandomStringUtils.randomAlphanumeric(8)); } catch (final ConfigurationNotFoundException ex) { logger.info("Error Asserted"); } @@ -81,11 +115,11 @@ public class TestEmailService extends AbstractNotificationTest { @Test public void shouldCreateAndRetrieveEmailConfigurationEntity() throws InterruptedException { logger.info("Create and Retrieve Email Gateway configuration"); - this.notificationManager.createEmailConfiguration(emailConfiguration); + this.testSubject.createEmailConfiguration(emailConfiguration); this.eventRecorder.wait(NotificationEventConstants.POST_EMAIL_CONFIGURATION, emailConfiguration.getIdentifier()); - EmailConfiguration sampleRetrieved = this.notificationManager.findEmailConfigurationByIdentifier(emailConfiguration.getIdentifier()); + EmailConfiguration sampleRetrieved = this.testSubject.findEmailConfigurationByIdentifier(emailConfiguration.getIdentifier()); Assert.assertNotNull(sampleRetrieved); Assert.assertEquals(sampleRetrieved.getIdentifier(), emailConfiguration.getIdentifier()); } @@ -93,7 +127,7 @@ public class TestEmailService extends AbstractNotificationTest { @Test public void checkEmailConfigurationEntityExist() throws InterruptedException { logger.info("Email Gateway configuration Exist"); - this.notificationManager.createEmailConfiguration(emailConfiguration); + this.testSubject.createEmailConfiguration(emailConfiguration); super.eventRecorder.wait(NotificationEventConstants.POST_EMAIL_CONFIGURATION, emailConfiguration.getIdentifier()); Assert.assertTrue(this.emailService.emailConfigurationExists(emailConfiguration.getIdentifier())); @@ -102,6 +136,6 @@ public class TestEmailService extends AbstractNotificationTest { @Test public void shouldFindActiveGateway() { this.logger.info("Find Active Gateway"); - Assert.assertNotNull(this.emailService.findActiveEmailConfigurationEntity()); + Assert.assertNotNull(this.emailService.getDefaultEmailConfigurationEntity()); } } diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSMSService.java b/component-test/src/main/java/org/apache/fineract/cn/notification/TestSMSService.java index 37ab85f..0a7738d 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSMSService.java +++ b/component-test/src/main/java/org/apache/fineract/cn/notification/TestSMSService.java @@ -20,8 +20,6 @@ package org.apache.fineract.cn.notification; import org.apache.commons.lang3.RandomStringUtils; import org.apache.fineract.cn.api.util.NotFoundException; -import org.apache.fineract.cn.customer.api.v1.client.CustomerNotFoundException; -import org.apache.fineract.cn.notification.api.v1.client.ConfigurationNotFoundException; import org.apache.fineract.cn.notification.api.v1.client.NotificationManager; import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; import org.apache.fineract.cn.notification.api.v1.events.NotificationEventConstants; @@ -71,7 +69,7 @@ public class TestSMSService extends AbstractNotificationTest { @Test public void shouldFindActiveGateway() { this.logger.info("Find Active Gateway"); - Assert.assertNotNull(this.smsService.findActiveSMSConfigurationEntity()); + Assert.assertNotNull(this.smsService.getDefaultSMSConfiguration()); } @Test diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java b/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java index ad4208a..2efc41c 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java +++ b/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java @@ -18,11 +18,14 @@ */ package org.apache.fineract.cn.notification; +import org.apache.fineract.cn.notification.importer.TestTemplateImport; import org.junit.runner.RunWith; import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) [email protected]({ +@SuiteClasses({ + TestTemplateImport.class, TestEmailService.class, TestSMSService.class, EmailApiDocumentation.class, diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/importer/TestTemplateImport.java b/component-test/src/main/java/org/apache/fineract/cn/notification/importer/TestTemplateImport.java new file mode 100644 index 0000000..57b2dc7 --- /dev/null +++ b/component-test/src/main/java/org/apache/fineract/cn/notification/importer/TestTemplateImport.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.cn.notification.importer; + +import org.apache.fineract.cn.notification.AbstractNotificationTest; +import org.apache.fineract.cn.notification.api.v1.domain.Template; +import org.apache.fineract.cn.notification.api.v1.events.NotificationEventConstants; +import org.apache.fineract.cn.notification.service.internal.importer.TemplateImporter; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.net.URL; + + +public class TestTemplateImport extends AbstractNotificationTest { + + public TestTemplateImport() { + super(); + } + + @Test + public void testTemplateImportHappyCase() throws IOException, InterruptedException { + final Template template = new Template(); + template.setTemplateIdentifier("test-sample"); + template.setSenderEmail("[email protected]"); + template.setSubject("Test"); + template.setMessage("Message"); + template.setUrl("test/url"); + + testSubject.createTemplate(template); + //Assert.assertTrue(eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, template.getTemplateIdentifier())); + eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, template.getTemplateIdentifier()); + + final TemplateImporter importer = new TemplateImporter(testSubject, logger); + final URL uri = ClassLoader.getSystemResource("importdata/test-templates.csv"); + importer.importCSV(uri); + //Assert.assertTrue(eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, "sample")); + eventRecorder.wait(NotificationEventConstants.POST_TEMPLATE, "sample"); + } +} diff --git a/component-test/src/main/resources/importdata/test-templates.csv b/component-test/src/main/resources/importdata/test-templates.csv new file mode 100644 index 0000000..d204bf1 --- /dev/null +++ b/component-test/src/main/resources/importdata/test-templates.csv @@ -0,0 +1,20 @@ +- +- Licensed to the Apache Software Foundation (ASF) under one +- or more contributor license agreements. See the NOTICE file +- distributed with this work for additional information +- regarding copyright ownership. The ASF licenses this file +- to you 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. +- +template_identifier,sender_email,subject,message,url +test_sample,DEFAULT,Test Sample,This is a test message,template diff --git a/component-test/src/main/resources/logback-test.xml b/component-test/src/main/resources/logback-test.xml new file mode 100644 index 0000000..a3c21c7 --- /dev/null +++ b/component-test/src/main/resources/logback-test.xml @@ -0,0 +1,35 @@ +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<configuration> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <logger name="org" level="ERROR"/> + <logger name="com" level="OFF"/> + <logger name="ch" level="OFF"/> + + <root level="DEBUG"> + <appender-ref ref="STDOUT"/> + </root> +</configuration> \ No newline at end of file diff --git a/component-test/src/main/resources/templates/template.html b/component-test/src/main/resources/templates/template.html new file mode 100644 index 0000000..d6a4d1c --- /dev/null +++ b/component-test/src/main/resources/templates/template.html @@ -0,0 +1,457 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> + +<!doctype html> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:th="http://www.thymeleaf.org"> +<head> + <meta name="viewport" content="width=device-width"/> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>Simple Email</title> + <link href="https://fonts.googleapis.com/css?family=helvatica" rel="stylesheet"/> + <style> + /* ------------------------------------- + GLOBAL RESETS + ------------------------------------- */ + + /*All the styling goes here*/ + + img { + border: none; + -ms-interpolation-mode: bicubic; + max-width: 100%; + } + + body { + background-color: #f6f6f6; + font-family: sans-serif; + -webkit-font-smoothing: antialiased; + font-size: 14px; + line-height: 1.4; + margin: 0; + padding: 0; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + } + + table { + border-collapse: separate; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + width: 100%; + } + + table td { + font-family: sans-serif; + font-size: 14px; + vertical-align: top; + } + + /* ------------------------------------- + CONTAINER + ------------------------------------- */ + + .body { + background-color: #f6f6f6; + width: 100%; + } + + /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */ + .container { + display: block; + margin: 0 auto !important; + /* makes it centered */ + max-width: 580px; + padding: 10px; + width: 580px; + } + + /* This should also be a block element, so that it will fill 100% of the .container */ + .content { + box-sizing: border-box; + display: block; + margin: 0 auto; + max-width: 580px; + padding: 10px; + } + + /* ------------------------------------- + HEADER, FOOTER, MAIN + ------------------------------------- */ + .main { + background: #ffffff; + border-radius: 3px; + width: 100%; + } + + .wrapper { + box-sizing: border-box; + padding: 20px; + } + + .content-block { + padding-bottom: 10px; + padding-top: 10px; + } + + .footer { + clear: both; + margin-top: 10px; + text-align: center; + width: 100%; + } + + .footer td, + .footer p, + .footer span, + .footer a { + color: #999999; + font-size: 12px; + text-align: center; + } + + /* ------------------------------------- + TYPOGRAPHY + ------------------------------------- */ + h1, + h2, + h3, + h4 { + color: #000000; + font-family: sans-serif; + font-weight: 400; + line-height: 1.4; + margin: 0; + margin-bottom: 30px; + } + + h1 { + font-size: 35px; + font-weight: 300; + text-align: center; + text-transform: capitalize; + } + + p, + ul, + ol { + font-family: sans-serif; + font-size: 14px; + font-weight: normal; + margin: 0; + margin-bottom: 15px; + } + + p li, + ul li, + ol li { + list-style-position: inside; + margin-left: 5px; + } + + a { + color: #a92334; + text-decoration: underline; + } + + /* ------------------------------------- + BUTTONS + ------------------------------------- */ + .btn { + box-sizing: border-box; + width: 100%; + } + + .btn > tbody > tr > td { + padding-bottom: 15px; + } + + .btn table { + width: auto; + } + + .btn table td { + background-color: #ffffff; + border-radius: 5px; + text-align: center; + } + + .btn a { + background-color: #ffffff; + border: solid 1px #a92334; + border-radius: 5px; + box-sizing: border-box; + color: #a92334; + cursor: pointer; + display: inline-block; + font-size: 14px; + font-weight: bold; + margin: 0; + padding: 12px 25px; + text-decoration: none; + text-transform: capitalize; + } + + .btn-primary table td { + background-color: #a92334; + } + + .btn-primary a { + background-color: #a92334; + border-color: #a92334; + color: #ffffff; + } + + /* ------------------------------------- + OTHER STYLES THAT MIGHT BE USEFUL + ------------------------------------- */ + .last { + margin-bottom: 0; + } + + .first { + margin-top: 0; + } + + .align-center { + text-align: center; + } + + .align-right { + text-align: right; + } + + .align-left { + text-align: left; + } + + .clear { + clear: both; + } + + .mt0 { + margin-top: 0; + } + + .mb0 { + margin-bottom: 0; + } + + .preheader { + color: transparent; + display: none; + height: 0; + max-height: 0; + max-width: 0; + opacity: 0; + overflow: hidden; + mso-hide: all; + visibility: hidden; + width: 0; + } + + .powered-by a { + text-decoration: none; + } + + hr { + border: 0; + border-bottom: 1px solid #f6f6f6; + margin: 20px 0; + } + + /* ------------------------------------- + RESPONSIVE AND MOBILE FRIENDLY STYLES + ------------------------------------- */ + @media only screen and (max-width: 620px) { + table[class=body] h1 { + font-size: 28px !important; + margin-bottom: 10px !important; + } + + table[class=body] p, + table[class=body] ul, + table[class=body] ol, + table[class=body] td, + table[class=body] span, + table[class=body] a { + font-size: 16px !important; + } + + table[class=body] .wrapper, + table[class=body] .article { + padding: 10px !important; + } + + table[class=body] .content { + padding: 0 !important; + } + + table[class=body] .container { + padding: 0 !important; + width: 100% !important; + } + + table[class=body] .main { + border-left-width: 0 !important; + border-radius: 0 !important; + border-right-width: 0 !important; + } + + table[class=body] .btn table { + width: 100% !important; + } + + table[class=body] .btn a { + width: 100% !important; + } + + table[class=body] .img-responsive { + height: auto !important; + max-width: 100% !important; + width: auto !important; + } + } + + /* ------------------------------------- + PRESERVE THESE STYLES IN THE HEAD + ------------------------------------- */ + @media all { + .headTab{ + height: 60px; + width: auto; + max-width: 540px; + padding: 10px; + margin: 0 auto; + background-color: #ffffff; + /*-webkit-box-shadow: 0px -1px 139px -25px rgba(0,0,0,0.69); + -moz-box-shadow: 0px -1px 139px -25px rgba(0,0,0,0.69); + box-shadow: 0px -1px 139px -25px rgba(0,0,0,0.69);*/ + } + .ExternalClass { + width: 100%; + } + + .ExternalClass, + .ExternalClass p, + .ExternalClass span, + .ExternalClass font, + .ExternalClass td, + .ExternalClass div { + line-height: 100%; + } + + .apple-link a { + color: inherit !important; + font-family: inherit !important; + font-size: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + text-decoration: none !important; + } + + .btn-primary table td:hover { + background-color: #a92334 !important; + } + + .btn-primary a:hover { + background-color: #a92334 !important; + border-color: #a92334 !important; + } + } + + </style> +</head> +<body> +<span class="preheader">Header Message</span> +<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body"> + + <tr> + <td> </td> + + <td class="container"> + <div class="headTab"><img style="height:40px; margin:10px;" class="img-responsive" src="http://fineract.apache.org/images/apache-fineract-logo.png" alt="Logo"></div> + + <div class="content"> + + <!-- START CENTERED WHITE CONTAINER --> + <table role="presentation" class="main"> + + <!-- START MAIN CONTENT AREA --> + <tr> + <td class="wrapper"> + <table role="presentation" border="0" cellpadding="0" cellspacing="0"> + <tr> + <td> + <p>Dear Valued Customer,</p> + + <p>This is a sample message an account update</p> + <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary"> + <tbody> + <tr> + <td align="left"> + <table role="presentation" border="0" cellpadding="0" cellspacing="0"> + <tbody> + <tr> + <td> <a href="http://fineract.apache.org" target="_blank">Call To Action</a> </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + <p>No reply required. This is a computer generated mail.</p> + <p>Best Regards,</p> + <p>MFI</p> + </td> + </tr> + </table> + </td> + </tr> + + <!-- END MAIN CONTENT AREA --> + </table> + <!-- END CENTERED WHITE CONTAINER --> + + <!-- START FOOTER --> + <div class="footer"> + <table role="presentation" border="0" cellpadding="0" cellspacing="0"> + <tr> + <td class="content-block"> + <span class="apple-link">Apache Software Foundation, Forest Hill, Maryland, United States</span> + <p> Don't like these emails? <a href="">Unsubscribe</a>.</p> + </td> + </tr> + <tr> + <td class="content-block powered-by"> + Powered by <a href="http://fineract.apache.org">Apache Fineract</a>. + </td> + </tr> + </table> + </div> + <!-- END FOOTER --> + + </div> + </td> + <td> </td> + </tr> +</table> +</body> +</html> diff --git a/service/build.gradle b/service/build.gradle index 6b8513e..6a9e068 100644 --- a/service/build.gradle +++ b/service/build.gradle @@ -49,6 +49,7 @@ dependencies { compile( [group: 'com.twilio.sdk', name: 'twilio', version: versions.twilioapi], [group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: versions.springjavamail], + [group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf'], [group: 'org.apache.fineract.cn.customer', name: 'api', version: versions.fineractcncustomer], [group: 'org.apache.fineract.cn.portfolio', name: 'api', version: versions.fineractcnportfolio], @@ -67,7 +68,9 @@ dependencies { [group: 'org.apache.fineract.cn', name: 'mariadb', version: versions.frameworkmariadb], [group: 'org.apache.fineract.cn', name: 'command', version: versions.frameworkcommand], [group: 'org.apache.fineract.cn.permitted-feign-client', name: 'library', version: versions.frameworkpermittedfeignclient], - [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator] + [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator], + [group: 'org.apache.commons', name: 'commons-csv', version: versions.apachecsvreader] + ) } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/ApplicationRepository.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/CreateTemplateCommand.java similarity index 53% rename from service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/ApplicationRepository.java rename to service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/CreateTemplateCommand.java index 9eb4b1c..9137439 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/ApplicationRepository.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/CreateTemplateCommand.java @@ -16,19 +16,27 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cn.notification.service.internal.repository; +package org.apache.fineract.cn.notification.service.internal.command; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Lock; -import org.springframework.stereotype.Repository; +import org.apache.fineract.cn.notification.api.v1.domain.Template; -import javax.persistence.LockModeType; -import java.util.Optional; - -@Repository -public interface ApplicationRepository extends JpaRepository<ApplicationEntity, Long> { - @Lock(LockModeType.PESSIMISTIC_WRITE) - void deleteByTenantIdentifierAndApplicationIdentifier(String tenantIdentifier, String applicationIdentifier); - - Optional<ApplicationEntity> findByTenantIdentifierAndApplicationIdentifier(String tenantIdentifier, String applicationIdentifier); +public class CreateTemplateCommand { + + private final Template template; + + public CreateTemplateCommand(final Template template) { + super(); + this.template = template; + } + + public Template getTemplate() { + return this.template; + } + + @Override + public String toString() { + return "CreateTemplateCommand{" + + "template=" + template + + '}'; + } } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/handler/ApplicationCommandHandler.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/handler/TemplateCommandHandler.java similarity index 55% rename from service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/handler/ApplicationCommandHandler.java rename to service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/handler/TemplateCommandHandler.java index 55de62d..5e60e27 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/handler/ApplicationCommandHandler.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/command/handler/TemplateCommandHandler.java @@ -22,42 +22,42 @@ import org.apache.fineract.cn.command.annotation.Aggregate; import org.apache.fineract.cn.command.annotation.CommandHandler; import org.apache.fineract.cn.command.annotation.CommandLogLevel; import org.apache.fineract.cn.command.annotation.EventEmitter; +import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; +import org.apache.fineract.cn.notification.api.v1.domain.Template; import org.apache.fineract.cn.notification.api.v1.events.NotificationEventConstants; -import org.apache.fineract.cn.notification.service.internal.command.SaveApplicationCommand; -import org.apache.fineract.cn.notification.service.internal.repository.ApplicationEntity; -import org.apache.fineract.cn.notification.service.internal.repository.ApplicationRepository; -import org.apache.fineract.cn.notification.service.internal.command.DeleteApplicationCommand; +import org.apache.fineract.cn.notification.service.internal.command.CreateSMSConfigurationCommand; +import org.apache.fineract.cn.notification.service.internal.command.CreateTemplateCommand; +import org.apache.fineract.cn.notification.service.internal.command.DeleteSMSConfigurationCommand; +import org.apache.fineract.cn.notification.service.internal.command.UpdateSMSConfigurationCommand; +import org.apache.fineract.cn.notification.service.internal.mapper.SMSConfigurationMapper; +import org.apache.fineract.cn.notification.service.internal.mapper.TemplateMapper; +import org.apache.fineract.cn.notification.service.internal.repository.SMSGatewayConfigurationEntity; +import org.apache.fineract.cn.notification.service.internal.repository.SMSGatewayConfigurationRepository; +import org.apache.fineract.cn.notification.service.internal.repository.TemplateEntity; +import org.apache.fineract.cn.notification.service.internal.repository.TemplateRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; @SuppressWarnings("unused") @Aggregate -public class ApplicationCommandHandler { - private final ApplicationRepository applicationRepository; - - @Autowired - public ApplicationCommandHandler( - final ApplicationRepository applicationRepository - ) { - super(); - this.applicationRepository = applicationRepository; - } - - @CommandHandler(logStart = CommandLogLevel.INFO,logFinish = CommandLogLevel.INFO) - @Transactional - @EventEmitter(selectorName = NotificationEventConstants.SELECTOR_NAME,selectorValue = NotificationEventConstants.POST_SOURCE_APPLICATION) - public String process(SaveApplicationCommand saveApplicationCommand){ - ApplicationEntity applicationEntity = new ApplicationEntity(); - applicationEntity.setApplicationIdentifier(saveApplicationCommand.getApplicationIdentifier()); - applicationEntity.setTenantIdentifier(saveApplicationCommand.getTenantIdentifier()); - this.applicationRepository.save(applicationEntity); - return saveApplicationCommand.getApplicationIdentifier(); - } +public class TemplateCommandHandler { + + private final TemplateRepository templateRepository; + + @Autowired + public TemplateCommandHandler(TemplateRepository templateRepository) { + super(); + this.templateRepository = templateRepository; + } @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO) @Transactional - @EventEmitter(selectorName = NotificationEventConstants.SELECTOR_NAME,selectorValue = NotificationEventConstants.DELETE_SOURCE_APPLICATION) - public void process(final DeleteApplicationCommand deleteApplicationCommand) { - this.applicationRepository.deleteByTenantIdentifierAndApplicationIdentifier(deleteApplicationCommand.getTenantIdentifier(), deleteApplicationCommand.getApplicationIdentifier()); + @EventEmitter(selectorName = NotificationEventConstants.SELECTOR_NAME, selectorValue = NotificationEventConstants.POST_TEMPLATE) + public String process(final CreateTemplateCommand createTemplateCommand) { + Template template = createTemplateCommand.getTemplate(); + final TemplateEntity entity = TemplateMapper.map(template); + this.templateRepository.save(entity); + + return template.getTemplateIdentifier(); } } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/config/NotificationConfiguration.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/config/NotificationConfiguration.java index 8db7f39..ba70234 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/config/NotificationConfiguration.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/config/NotificationConfiguration.java @@ -52,6 +52,11 @@ import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.jms.core.JmsTemplate; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.thymeleaf.spring4.SpringTemplateEngine; +import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver; +import org.thymeleaf.templatemode.StandardTemplateModeHandlers; + +import java.nio.charset.StandardCharsets; @SuppressWarnings("WeakerAccess") @Configuration @@ -103,23 +108,24 @@ public class NotificationConfiguration extends WebMvcConfigurerAdapter { } @Bean - public PooledConnectionFactory jmsFactory() { + public PooledConnectionFactory pooledConnectionFactory() { PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(); ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(); - activeMQConnectionFactory.setBrokerURL(this.environment.getProperty("activemq.brokerUrl", "vm://localhost?broker.persistent=falseac")); + activeMQConnectionFactory.setBrokerURL(this.environment.getProperty("activemq.brokerUrl","vm://localhost?broker.persistent=false")); pooledConnectionFactory.setConnectionFactory(activeMQConnectionFactory); return pooledConnectionFactory; } @Bean public JmsListenerContainerFactory jmsListenerContainerFactory(PooledConnectionFactory jmsFactory) { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + final DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setConnectionFactory(jmsFactory); factory.setPubSubDomain(true); factory.setConnectionFactory(jmsFactory); factory.setErrorHandler(ex -> { - loggerBean().error(ex.getCause().toString()); + loggerBean().warn(ex.getCause().toString()); }); - factory.setConcurrency(this.environment.getProperty("activemq.concurrency", "1-1")); + factory.setConcurrency(this.environment.getProperty("activemq.concurrency","1-1")); return factory; } @@ -133,6 +139,23 @@ public class NotificationConfiguration extends WebMvcConfigurerAdapter { return jmsTemplate; } + @Bean + public SpringTemplateEngine springTemplateEngine() { + SpringTemplateEngine templateEngine = new SpringTemplateEngine(); + templateEngine.addTemplateResolver(htmlTemplateResolver()); + return templateEngine; + } + + @Bean + public SpringResourceTemplateResolver htmlTemplateResolver(){ + SpringResourceTemplateResolver emailTemplateResolver = new SpringResourceTemplateResolver(); + emailTemplateResolver.setPrefix("classpath:/templates/"); + emailTemplateResolver.setSuffix(".html"); + emailTemplateResolver.setTemplateMode(StandardTemplateModeHandlers.HTML5.getTemplateModeName()); + emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name()); + return emailTemplateResolver; + } + @Bean( name = {ServiceConstants.LOGGER_NAME} ) diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/importer/TemplateImporter.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/importer/TemplateImporter.java new file mode 100644 index 0000000..ee2782f --- /dev/null +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/importer/TemplateImporter.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.cn.notification.service.internal.importer; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.fineract.cn.notification.api.v1.client.NotificationManager; +import org.apache.fineract.cn.notification.api.v1.client.TemplateAlreadyExistException; +import org.apache.fineract.cn.notification.api.v1.domain.Template; +import org.slf4j.Logger; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * @author Ebenezer Graham + */ +@SuppressWarnings("unused") +public class TemplateImporter { + private static final String TEMPLATE_IDENTIFIER_COLUMN = "template_identifier"; + private static final String SENDER_EMAIL_COLUMN = "sender_email"; + private static final String SUBJECT_COLUMN = "subject"; + private static final String MESSAGE_COLUMN = "message"; + private static final String URL_COLUMN = "url"; + + private final NotificationManager notificationManager; + private final Logger logger; + + public TemplateImporter(final NotificationManager notificationManager, final Logger logger) { + this.notificationManager = notificationManager; + this.logger = logger; + } + + public void importCSV(final URL toImport) throws IOException { + final CSVParser parser = CSVParser.parse(toImport, StandardCharsets.UTF_8, CSVFormat.RFC4180.withHeader().withCommentMarker('-')); + final List<Template>templatesList = StreamSupport.stream(parser.spliterator(), false) + .map(this::toTemplate) + .collect(Collectors.toList()); + templatesList.forEach(this::createTemplate); + } + + private void createTemplate(final Template toCreate) { + try { + notificationManager.createTemplate(toCreate); + } + catch (final TemplateAlreadyExistException ignored) { + logger.error("Creation of template {} failed, because a template with the same identifier but different properties already exists {}", toCreate.getTemplateIdentifier(),toCreate.toString()); + } + } + + private Template toTemplate(final CSVRecord csvRecord) { + try { + final String templateIdentifier = csvRecord.get(TEMPLATE_IDENTIFIER_COLUMN); + final String subject = csvRecord.get(SUBJECT_COLUMN); + final String senderEmail = csvRecord.get(SENDER_EMAIL_COLUMN); + final String url = csvRecord.get(URL_COLUMN); + String message; + try { + message = csvRecord.get(MESSAGE_COLUMN); + } + catch (final NullPointerException e) { + message = "Do not reply, This is a computer generate message."; + } + return new Template(templateIdentifier,senderEmail,subject,message,url); + } + catch (final IllegalArgumentException e) { + logger.warn("Parsing failed on record {}", csvRecord.getRecordNumber()); + throw e; + } + } +} diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/mapper/EmailConfigurationMapper.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/mapper/EmailConfigurationMapper.java index 5dcfbae..9f8b024 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/mapper/EmailConfigurationMapper.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/mapper/EmailConfigurationMapper.java @@ -40,6 +40,10 @@ public class EmailConfigurationMapper { emailConfiguration.setPort(emailGatewayConfigurationEntity.getPort()); emailConfiguration.setUsername(emailGatewayConfigurationEntity.getUsername()); emailConfiguration.setApp_password(emailGatewayConfigurationEntity.getApp_password()); + emailConfiguration.setProtocol(emailGatewayConfigurationEntity.getProtocol()); + emailConfiguration.setSmtp_auth(emailGatewayConfigurationEntity.getSmtp_auth()); + emailConfiguration.setStart_tls(emailGatewayConfigurationEntity.getStart_tls()); + emailConfiguration.setState(emailGatewayConfigurationEntity.getState()); return emailConfiguration; } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/mapper/TemplateMapper.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/mapper/TemplateMapper.java new file mode 100644 index 0000000..257cd07 --- /dev/null +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/mapper/TemplateMapper.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.cn.notification.service.internal.mapper; + +import org.apache.fineract.cn.notification.api.v1.domain.Template; +import org.apache.fineract.cn.notification.service.internal.repository.TemplateEntity; + +public class TemplateMapper { + + private TemplateMapper() { + super(); + } + + public static Template map(final TemplateEntity templateEntity) { + final Template template = new Template(); + template.setTemplateIdentifier(templateEntity.getTemplateIdentifier()); + template.setSenderEmail(templateEntity.getSenderEmail()); + template.setSubject(templateEntity.getSubject()); + template.setMessage(templateEntity.getMessage()); + template.setUrl(templateEntity.getUrl()); + return template; + } + + public static TemplateEntity map(final Template template) { + final TemplateEntity templateEntity = new TemplateEntity(); + templateEntity.setTemplateIdentifier(template.getTemplateIdentifier()); + templateEntity.setSubject(template.getSubject()); + templateEntity.setSenderEmail(template.getSenderEmail()); + templateEntity.setMessage(template.getMessage()); + templateEntity.setUrl(template.getUrl()); + return templateEntity; + } +} + diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/ApplicationEntity.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/ApplicationEntity.java deleted file mode 100644 index 0e22a04..0000000 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/ApplicationEntity.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.fineract.cn.notification.service.internal.repository; - -import javax.persistence.*; -import java.util.Objects; - -@SuppressWarnings({"unused", "WeakerAccess"}) -@Entity -@Table(name = "wada_data_source_application") -public class ApplicationEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Column(name = "tenant_identifier", nullable = false) - private String tenantIdentifier; - - @Column(name = "application_identifier", nullable = false) - private String applicationIdentifier; - - @Column(name = "permittable_identifier") - private String permittableGroupIdentifier; - - public ApplicationEntity() { - } - - public ApplicationEntity(String tenantIdentifier, String applicationIdentifier, String permittableGroupIdentifier) { - this.tenantIdentifier = tenantIdentifier; - this.applicationIdentifier = applicationIdentifier; - this.permittableGroupIdentifier = permittableGroupIdentifier; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTenantIdentifier() { - return tenantIdentifier; - } - - public void setTenantIdentifier(String tenantIdentifier) { - this.tenantIdentifier = tenantIdentifier; - } - - public String getApplicationIdentifier() { - return applicationIdentifier; - } - - public void setApplicationIdentifier(String applicationIdentifier) { - this.applicationIdentifier = applicationIdentifier; - } - - public String getPermittableGroupIdentifier() { - return permittableGroupIdentifier; - } - - public void setPermittableGroupIdentifier(String permittableGroupIdentifier) { - this.permittableGroupIdentifier = permittableGroupIdentifier; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ApplicationEntity that = (ApplicationEntity) o; - return Objects.equals(tenantIdentifier, that.tenantIdentifier) && - Objects.equals(applicationIdentifier, that.applicationIdentifier); - } - - @Override - public int hashCode() { - return Objects.hash(tenantIdentifier, applicationIdentifier); - } - - @Override - public String toString() { - return "ApplicationEntity{" + - "id=" + id + - ", tenantIdentifier='" + tenantIdentifier + '\'' + - ", applicationIdentifier='" + applicationIdentifier + '\'' + - ", permittableGroupIdentifier='" + permittableGroupIdentifier + '\'' + - '}'; - } -} diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/EmailGatewayConfigurationRepository.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/EmailGatewayConfigurationRepository.java index 93785be..2b5ddcb 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/EmailGatewayConfigurationRepository.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/EmailGatewayConfigurationRepository.java @@ -32,8 +32,8 @@ public interface EmailGatewayConfigurationRepository extends JpaRepository<Email @Query("SELECT CASE WHEN COUNT(c) > 0 THEN 'true' ELSE 'false' END FROM EmailGatewayConfigurationEntity c WHERE c.identifier = :identifier") Boolean existsByIdentifier(@Param("identifier") final String identifier); - @Query("SELECT entity FROM EmailGatewayConfigurationEntity entity WHERE entity.state='ACTIVE'") - Optional<EmailGatewayConfigurationEntity> active(); + @Query("SELECT entity FROM EmailGatewayConfigurationEntity entity WHERE entity.identifier='DEFAULT'") + Optional<EmailGatewayConfigurationEntity> defaultGateway(); void deleteEmailGatewayConfigurationEntityBy(String identifier); } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/SMSGatewayConfigurationRepository.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/SMSGatewayConfigurationRepository.java index 9b440e5..64e8427 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/SMSGatewayConfigurationRepository.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/SMSGatewayConfigurationRepository.java @@ -32,8 +32,8 @@ public interface SMSGatewayConfigurationRepository extends JpaRepository<SMSGate @Query("SELECT CASE WHEN COUNT(c) > 0 THEN 'true' ELSE 'false' END FROM SMSGatewayConfigurationEntity c WHERE c.identifier = :identifier") Boolean existsByIdentifier(@Param("identifier") final String identifier); - @Query("SELECT entity FROM SMSGatewayConfigurationEntity entity WHERE entity.state='ACTIVE'") - Optional<SMSGatewayConfigurationEntity> active(); + @Query("SELECT entity FROM SMSGatewayConfigurationEntity entity WHERE entity.identifier='DEFAULT'") + Optional<SMSGatewayConfigurationEntity> defaultGateway(); void deleteSMSGatewayConfigurationEntityBy(String identifier); } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateEntity.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateEntity.java index c699b9c..4f6f3c8 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateEntity.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateEntity.java @@ -30,10 +30,14 @@ public class TemplateEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; - @Column(name = "identifier") - private String identifier; - @Column(name = "event") - private String event; + @Column(name = "template_identifier") + private String templateIdentifier; + @Column(name = "subject") + private String subject; + @Column(name = "sender_email") + private String senderEmail; + @Column(name = "message") + private String message; @Column(name = "url") private String url; @@ -50,20 +54,36 @@ public class TemplateEntity { this.id = id; } - public String getIdentifier() { - return this.identifier; + public String getTemplateIdentifier() { + return templateIdentifier; } - public void setIdentifier(final String identifier) { - this.identifier = identifier; + public void setTemplateIdentifier(String templateIdentifier) { + this.templateIdentifier = templateIdentifier; } - public String getEvent() { - return event; + public String getSubject() { + return subject; } - public void setEvent(String event) { - this.event = event; + public void setSubject(String subject) { + this.subject = subject; + } + + public String getSenderEmail() { + return senderEmail; + } + + public void setSenderEmail(String senderEmail) { + this.senderEmail = senderEmail; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; } public String getUrl() { @@ -80,22 +100,24 @@ public class TemplateEntity { if (o == null || getClass() != o.getClass()) return false; TemplateEntity that = (TemplateEntity) o; return Objects.equals(id, that.id) && - Objects.equals(identifier, that.identifier) && - Objects.equals(event, that.event) && + Objects.equals(templateIdentifier, that.templateIdentifier) && + Objects.equals(subject, that.subject) && Objects.equals(url, that.url); } @Override public int hashCode() { - return Objects.hash(id, identifier, event, url); + return Objects.hash(id, templateIdentifier, subject, message, url); } @Override public String toString() { return "TemplateEntity{" + "id=" + id + - ", identifier='" + identifier + '\'' + - ", event='" + event + '\'' + + ", templateIdentifier='" + templateIdentifier + '\'' + + ", subject='" + subject + '\'' + + ", message='" + message + '\'' + + ", senderEmail='" + senderEmail + '\'' + ", url='" + url + '\'' + '}'; } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateRepository.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateRepository.java index 5e82d1f..cbb7b3f 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateRepository.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/repository/TemplateRepository.java @@ -19,11 +19,17 @@ package org.apache.fineract.cn.notification.service.internal.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface TemplateRepository extends JpaRepository<TemplateEntity, Long> { - Optional<TemplateEntity> findByIdentifier(String identifier); + Optional<TemplateEntity> findByTemplateIdentifier(String identifier); + + @Query("SELECT CASE WHEN COUNT(c) > 0 THEN 'true' ELSE 'false' END FROM TemplateEntity c WHERE c.templateIdentifier = :template_identifier") + Boolean existsByTemplateIdentifier(@Param("template_identifier") final String identifier); + } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/EmailService.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/EmailService.java index bc006c6..f1f24ed 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/EmailService.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/EmailService.java @@ -28,17 +28,20 @@ import org.apache.fineract.cn.notification.service.ServiceConstants; import org.apache.fineract.cn.notification.service.internal.mapper.EmailConfigurationMapper; import org.apache.fineract.cn.notification.service.internal.repository.EmailGatewayConfigurationRepository; +import org.apache.fineract.cn.notification.service.internal.service.util.MailBuilder; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.mail.javamail.MimeMessagePreparator; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.PostConstruct; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -46,56 +49,35 @@ import java.util.Properties; @Aggregate public class EmailService { - static boolean isConfigured; - private final EmailGatewayConfigurationRepository emailGatewayConfigurationRepository; + boolean isConfigured; private JavaMailSenderImpl mailSender; - + private MailBuilder mailBuilder; private Logger logger; - private String host; - private String email; - private int port; - private String password; @Autowired public EmailService(final EmailGatewayConfigurationRepository emailGatewayConfigurationRepository, + final MailBuilder mailBuilder, @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) { super(); this.isConfigured = false; this.logger = logger; this.mailSender = new JavaMailSenderImpl(); this.emailGatewayConfigurationRepository = emailGatewayConfigurationRepository; + this.mailBuilder = mailBuilder; } - //@PostConstruct - public void init() { - if (findActiveEmailConfigurationEntity().isPresent()){ - configureEmailGatewayWithActiveConfiguration(); - }else{ - //Todo: Send an alert on the interface to configure the service - } - } - - public boolean configureEmailGatewayWithActiveConfiguration() { - EmailConfiguration configuration = findActiveEmailConfigurationEntity().get(); - - this.host = configuration.getHost(); - this.email = configuration.getUsername(); - this.port = Integer.parseInt(configuration.getPort()); - this.password = configuration.getApp_password(); - return this.isConfigured = setJavaMailSender(); - } - - public boolean customConfiguration(String identifier) { - return this.isConfigured = setCustomProperties(identifier); + public boolean configureEmailGatewayWithDefaultGateway() { + EmailConfiguration configuration = getDefaultEmailConfigurationEntity().get(); + return setNewConfiguration(configuration); } - public List<EmailConfiguration> findAllActiveEmailConfigurationEntities() { + public List<EmailConfiguration> findAllEmailConfigurationEntities() { return EmailConfigurationMapper.map(this.emailGatewayConfigurationRepository.findAll()); } - public Optional<EmailConfiguration> findActiveEmailConfigurationEntity() { - return this.emailGatewayConfigurationRepository.active().map(EmailConfigurationMapper::map); + public Optional<EmailConfiguration> getDefaultEmailConfigurationEntity() { + return this.emailGatewayConfigurationRepository.defaultGateway().map(EmailConfigurationMapper::map); } public Optional<EmailConfiguration> findEmailConfigurationByIdentifier(final String identifier) { @@ -106,65 +88,69 @@ public class EmailService { return this.emailGatewayConfigurationRepository.existsByIdentifier(identifier); } - public boolean setJavaMailSender() { - mailSender.setHost(host); - mailSender.setPort(port); - mailSender.setUsername(email); - mailSender.setPassword(password); - - switch (host.toLowerCase()) { - case ServiceConstants.GOOGLE_MAIL_SERVER: - return setProperties(); - case ServiceConstants.YAHOO_MAIL_SERVER: - return setProperties(); - } - return false; - } - - public boolean setProperties() { - Properties properties = new Properties(); - properties.put(ServiceConstants.MAIL_TRANSPORT_PROTOCOL_PROPERTY, - ServiceConstants.MAIL_TRANSPORT_PROTOCOL_VALUE); - properties.put(ServiceConstants.MAIL_SMTP_AUTH_PROPERTY, - ServiceConstants.MAIL_SMTP_AUTH_VALUE); - properties.put(ServiceConstants.MAIL_SMTP_STARTTLS_ENABLE_PROPERTY, - ServiceConstants.MAIL_SMTP_STARTTLS_ENABLE_VALUE); - this.mailSender.setJavaMailProperties(properties); - return true; + boolean setNewConfiguration(String identifier) { + EmailConfiguration configuration = findEmailConfigurationByIdentifier(identifier).get(); + return setNewConfiguration(configuration); } - public boolean setCustomProperties(String identifier) { - EmailConfiguration configuration = findEmailConfigurationByIdentifier(identifier).get(); - this.mailSender.setHost(configuration.getHost()); - this.mailSender.setPort(Integer.parseInt(configuration.getPort())); - this.mailSender.setUsername(configuration.getUsername()); - this.mailSender.setPassword(configuration.getApp_password()); - - Properties properties = new Properties(); - properties.put(ServiceConstants.MAIL_TRANSPORT_PROTOCOL_PROPERTY, configuration.getProtocol()); - properties.put(ServiceConstants.MAIL_SMTP_AUTH_PROPERTY, configuration.getSmtp_auth()); - properties.put(ServiceConstants.MAIL_SMTP_STARTTLS_ENABLE_PROPERTY, configuration.getStart_tls()); - //properties.put(ServiceConstants.MAIL_SMTP_TIMEOUT_PROPERTY, ServiceConstants.MAIL_SMTP_TIMEOUT_VALUE); - this.mailSender.setJavaMailProperties(properties); - return true; + private boolean setNewConfiguration(EmailConfiguration configuration) { + try { + this.mailSender.setHost(configuration.getHost()); + this.mailSender.setPort(Integer.parseInt(configuration.getPort())); + this.mailSender.setUsername(configuration.getUsername()); + this.mailSender.setPassword(configuration.getApp_password()); + + Properties properties = new Properties(); + properties.put(ServiceConstants.MAIL_TRANSPORT_PROTOCOL_PROPERTY, configuration.getProtocol()); + properties.put(ServiceConstants.MAIL_SMTP_AUTH_PROPERTY, configuration.getSmtp_auth()); + properties.put(ServiceConstants.MAIL_SMTP_STARTTLS_ENABLE_PROPERTY, configuration.getStart_tls()); + this.mailSender.setJavaMailProperties(properties); + this.isConfigured = true; + return true; + } catch (RuntimeException ignore) { + logger.error("Failed to configure the Email Gateway"); + } + return false; } @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO) @Transactional @EventEmitter(selectorName = NotificationEventConstants.SELECTOR_NAME, selectorValue = NotificationEventConstants.POST_SEND_EMAIL_NOTIFICATION) - public String sendEmail(String from, String to, String subject, String message) { + public String sendPlainEmail(String to, String subject, String message) { SimpleMailMessage mail = new SimpleMailMessage(); try { - mail.setFrom(from); mail.setTo(to); mail.setSubject(subject); mail.setText(message); - this.mailSender.send(mail); + return to; } catch (MailException exception) { logger.debug("Caused by:" + exception.getCause().toString()); } - return to.concat(" - " + mailSender.hashCode()); + return null; + } + + @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO) + @Transactional + @EventEmitter(selectorName = NotificationEventConstants.SELECTOR_NAME, selectorValue = NotificationEventConstants.POST_SEND_EMAIL_NOTIFICATION) + public String sendFormattedEmail(String to, + String subject, + Map<String, Object> message, + String emailTemplate) { + MimeMessagePreparator messagePreparator = mimeMessage -> { + MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage); + messageHelper.setTo(to); + messageHelper.setSubject(subject); + String content = mailBuilder.build(message, emailTemplate); + messageHelper.setText(content, true); + }; + try { + this.mailSender.send(messagePreparator); + return to; + } catch (MailException e) { + logger.error("Failed to send Formatted email{}", e.getMessage()); + } + return null; } -} \ No newline at end of file +} diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/NotificationService.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/NotificationService.java index 02b3621..2b51e8c 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/NotificationService.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/NotificationService.java @@ -19,6 +19,7 @@ package org.apache.fineract.cn.notification.service.internal.service; import org.apache.fineract.cn.customer.api.v1.domain.Customer; +import org.apache.fineract.cn.notification.api.v1.domain.Template; import org.apache.fineract.cn.notification.service.ServiceConstants; import org.apache.fineract.cn.notification.service.internal.identity.CustomerPermittedClient; import org.apache.fineract.cn.notification.service.internal.identity.NotificationAuthentication; @@ -28,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import java.util.Map; import java.util.Optional; @Service @@ -35,6 +37,7 @@ public class NotificationService { private final SMSService smsService; private final EmailService emailService; + private final TemplateService templateService; private final NotificationAuthentication notificationAuthentication; private final CustomerService customerService; @@ -46,6 +49,7 @@ public class NotificationService { public NotificationService(final CustomerService customerService, final SMSService smsService, final EmailService emailService, + final TemplateService templateService, final NotificationAuthentication notificationAuthentication, final CustomerPermittedClient customerPermittedClient, @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger @@ -54,6 +58,7 @@ public class NotificationService { this.customerService = customerService; this.smsService = smsService; this.emailService = emailService; + this.templateService = templateService; this.notificationAuthentication = notificationAuthentication; this.customerPermittedClient = customerPermittedClient; this.logger = logger; @@ -65,25 +70,26 @@ public class NotificationService { return customerService.findCustomer(customerIdentifier); } - //SMS Related Operations - public SMSService setNewSMSService(SMSService smsService, String configurationId){ - smsService.customConfiguration(configurationId); - return smsService; - } - public String sendSMS(String receiver, String template) { - if (!this.smsService.isConfigured) this.smsService.configureSMSGatewayWithActiveConfiguration(); + if (!this.smsService.isConfigured) this.smsService.configureServiceWithDefaultGateway(); return this.smsService.sendSMS(receiver, template); } - //Email Related Operations - public String sendEmail(String from, String to, String subject, String message) { - if (!emailService.isConfigured) emailService.configureEmailGatewayWithActiveConfiguration(); - return this.emailService.sendEmail(from, to, subject, message); + /*To be used as a backup should Formatted email fail*/ + public void sendEmail(String to, String templateIdentifier,Object payload) { + Template template = this.templateService.findTemplateWithIdentifier(templateIdentifier).get(); + if (!this.emailService.isConfigured) { + this.emailService.setNewConfiguration(template.getSenderEmail()); + } + this.emailService.sendPlainEmail(to, template.getSubject(), template.getMessage()); } - public EmailService setNewEmailService(EmailService emailService, String configurationId){ - emailService.customConfiguration(configurationId); - return emailService; + public void sendFormattedEmail(String to, String templateIdentifier, Map<String,Object> variables) { + Template template = this.templateService.findTemplateWithIdentifier(templateIdentifier).get(); + if (!this.emailService.isConfigured) { + this.emailService.setNewConfiguration(template.getSenderEmail()); + } + + this.emailService.sendFormattedEmail(to, template.getSubject(), variables,template.getUrl()); } } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/SMSService.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/SMSService.java index f618d1a..966aee3 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/SMSService.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/SMSService.java @@ -34,11 +34,9 @@ import org.apache.fineract.cn.notification.service.internal.repository.SMSGatewa import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.PostConstruct; import java.util.List; import java.util.Optional; @@ -63,15 +61,15 @@ public class SMSService { //@PostConstruct public void init() { - if (findActiveSMSConfigurationEntity().isPresent()){ - configureSMSGatewayWithActiveConfiguration(); + if (getDefaultSMSConfiguration().isPresent()){ + configureServiceWithDefaultGateway(); }else{ //Todo: Send an alert on the interface to configure the service } } - public boolean configureSMSGatewayWithActiveConfiguration() { - SMSConfiguration configuration = findActiveSMSConfigurationEntity().get(); + public boolean configureServiceWithDefaultGateway() { + SMSConfiguration configuration = getDefaultSMSConfiguration().get(); this.accountSid = configuration.getAccount_sid(); this.authToken = configuration.getAuth_token(); this.senderNumber = configuration.getSender_number(); @@ -86,8 +84,8 @@ public class SMSService { return this.isConfigured = true; } - public Optional<SMSConfiguration> findActiveSMSConfigurationEntity() { - return this.smsGatewayConfigurationRepository.active().map(SMSConfigurationMapper::map); + public Optional<SMSConfiguration> getDefaultSMSConfiguration() { + return this.smsGatewayConfigurationRepository.defaultGateway().map(SMSConfigurationMapper::map); } public Boolean smsConfigurationExists(final String identifier) { @@ -98,7 +96,7 @@ public class SMSService { return this.smsGatewayConfigurationRepository.findByIdentifier(identifier).map(SMSConfigurationMapper::map); } - public List<SMSConfiguration> findAllActiveSMSConfigurationEntities() { + public List<SMSConfiguration> findAllSMSConfigurationEntities() { return SMSConfigurationMapper.map(this.smsGatewayConfigurationRepository.findAll()); } @@ -114,4 +112,4 @@ public class SMSService { Message message = messageCreator.create(); return message.getTo().concat(" - " + message.getSid()); } -} \ No newline at end of file +} diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/TemplateService.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/TemplateService.java new file mode 100644 index 0000000..055403b --- /dev/null +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/TemplateService.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.cn.notification.service.internal.service; + +import org.apache.fineract.cn.command.annotation.Aggregate; +import org.apache.fineract.cn.notification.api.v1.domain.Template; +import org.apache.fineract.cn.notification.service.ServiceConstants; +import org.apache.fineract.cn.notification.service.internal.mapper.TemplateMapper; +import org.apache.fineract.cn.notification.service.internal.repository.TemplateRepository; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@Aggregate +public class TemplateService { + + + private final TemplateRepository templateRepository; + private Logger logger; + + @Autowired + public TemplateService(final TemplateRepository templateRepository, + @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) { + super(); + this.logger = logger; + this.templateRepository = templateRepository; + } + public Optional<Template> findTemplateWithIdentifier(final String identifier) { + return this.templateRepository.findByTemplateIdentifier(identifier).map(TemplateMapper::map); + } + + public Boolean templateExists(final String identifier) { + return this.templateRepository.existsByTemplateIdentifier(identifier); + } + + public Boolean deleteTemplate(final String identifier) { + //Todo: Remove html template and template record from repository + return false; + } +} diff --git a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/util/MailBuilder.java similarity index 50% copy from component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java copy to service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/util/MailBuilder.java index ad4208a..e12c87f 100644 --- a/component-test/src/main/java/org/apache/fineract/cn/notification/TestSuite.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/internal/service/util/MailBuilder.java @@ -16,17 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cn.notification; +package org.apache.fineract.cn.notification.service.internal.service.util; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; -@RunWith(Suite.class) [email protected]({ - TestEmailService.class, - TestSMSService.class, - EmailApiDocumentation.class, - SmsApiDocumentation.class, -}) -public class TestSuite extends SuiteTestEnvironment { +import java.util.Map; +/* +ebenezergraham created on 5/12/19 +*/ +@Service +public class MailBuilder { + private TemplateEngine templateEngine; + + @Autowired + public MailBuilder(TemplateEngine templateEngine) { + this.templateEngine = templateEngine; + } + + public String build(Map<String, Object> message, String template) { + Context context = new Context(); + for(Map.Entry m:message.entrySet()){ + context.setVariable(m.getKey().toString(), m.getValue().toString()); + } + return templateEngine.process(template, context); + } } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/listener/CustomerEventListener.java b/service/src/main/java/org/apache/fineract/cn/notification/service/listener/CustomerEventListener.java index ce5325e..96524e4 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/listener/CustomerEventListener.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/listener/CustomerEventListener.java @@ -84,13 +84,10 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", + notificationService.sendEmail( emailAddress, - "Account created", - "Dear Valued Customer," + - "\n\nYour account has been created" + - "\n\nBest Regards," + - "\nYour MFI"); + "customerCreatedEvent", + payload); } }); } @@ -118,13 +115,10 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", + notificationService.sendEmail( emailAddress, - "Account updated", - "Dear Valued Customer," + - "\n\nYour account has been Updated" + - "\n\nBest Regards," + - "\nYour MFI"); + "customerUpdatedEvents", + payload); } }); } @@ -150,13 +144,10 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", + notificationService.sendEmail( emailAddress, - "Account activated", - "Dear Valued Customer," + - "\n\nYour account has been Activated" + - "\n\nBest Regards," + - "\nYour MFI"); + "customerActivatedEvent", + payload); } }); } @@ -182,12 +173,9 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", - emailAddress, "Account locked", - "Dear Valued Customer," + - "\n\nYour account has been Locked" + - "\n\nBest Regards," + - "\nYour MFI"); + notificationService.sendEmail( + emailAddress, "customerLockedEvent", + payload); } }); } @@ -213,13 +201,10 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", + notificationService.sendEmail( emailAddress, - "Account created", - "Dear Valued Customer," + - "\n\nYour account has been Unlocked" + - "\n\nBest Regards," + - "\nYour MFI"); + "customerUnlockedEvent", + payload); } }); } @@ -245,12 +230,9 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", - emailAddress, "Account closed", - "Dear Valued Customer," + - "\n\nYour account has been Closed" + - "\n\nBest Regards," + - "\nYour MFI"); + notificationService.sendEmail( + emailAddress, "customerClosedEvent", + payload); } }); } @@ -276,13 +258,10 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", + notificationService.sendEmail( emailAddress, - "Account Reopened", - "Dear Valued Customer," + - "\n\nYour account has been reopened" + - "\n\nBest Regards," + - "\nYour MFI"); + "customerReopenedEvent", + payload); } }); } @@ -309,14 +288,10 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", + notificationService.sendEmail( emailAddress, - "Contact Details Changed", - "Dear Valued Customer," + - "\n\nYour contact has been changed successfully" + - "\n\tNew Contact: "+emailAddress+ - "\n\nBest Regards" + - "\nYour MFI"); + "contactDetailsChangedEvent", + payload); } }); } @@ -347,19 +322,11 @@ String emailAddress = contact.getValue(); // TODO: Localize message // TODO: Pass message to template - notificationService.sendEmail("[email protected]", + notificationService.sendEmail( emailAddress, - "Contact Details Changed" + - "New Contact: "+emailAddress, - "Dear Valued Customer," + - "\n\nYour address has been changed successfully" + - "\nStreet: "+ customer.getAddress().getStreet() + - "\nCity: "+ customer.getAddress().getCity() + - "\nState: "+ customer.getAddress().getRegion() + - "\nCountry: "+ customer.getAddress().getCountry() + - "\n\nBest Regards" + - "\nYour MFI"); + "addressChangedEvent", + payload); } }); } - } \ No newline at end of file + } diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/rest/EmailServiceRestController.java b/service/src/main/java/org/apache/fineract/cn/notification/service/rest/EmailServiceRestController.java index 2823b6d..d1859a2 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/rest/EmailServiceRestController.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/rest/EmailServiceRestController.java @@ -24,11 +24,9 @@ import org.apache.fineract.cn.command.gateway.CommandGateway; import org.apache.fineract.cn.lang.ServiceException; import org.apache.fineract.cn.notification.api.v1.PermittableGroupIds; import org.apache.fineract.cn.notification.api.v1.domain.EmailConfiguration; -import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; import org.apache.fineract.cn.notification.service.ServiceConstants; import org.apache.fineract.cn.notification.service.internal.command.*; import org.apache.fineract.cn.notification.service.internal.service.EmailService; -import org.apache.fineract.cn.notification.service.internal.service.NotificationService; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -95,7 +93,7 @@ public class EmailServiceRestController { public @ResponseBody List<EmailConfiguration> findAllActiveEmailConfigurationEntities() { - return this.emailService.findAllActiveEmailConfigurationEntities(); + return this.emailService.findAllEmailConfigurationEntities(); } @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT) diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/rest/SMSServiceRestController.java b/service/src/main/java/org/apache/fineract/cn/notification/service/rest/SMSServiceRestController.java index b9a6e83..26106bc 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/rest/SMSServiceRestController.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/rest/SMSServiceRestController.java @@ -24,10 +24,8 @@ import org.apache.fineract.cn.command.gateway.CommandGateway; import org.apache.fineract.cn.lang.ServiceException; import org.apache.fineract.cn.notification.api.v1.PermittableGroupIds; import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; -import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; import org.apache.fineract.cn.notification.service.ServiceConstants; import org.apache.fineract.cn.notification.service.internal.command.*; -import org.apache.fineract.cn.notification.service.internal.service.NotificationService; import org.apache.fineract.cn.notification.service.internal.service.SMSService; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -69,7 +67,7 @@ public class SMSServiceRestController { public @ResponseBody List<SMSConfiguration> findAllActiveSMSConfigurationEntities() { - return this.smsService.findAllActiveSMSConfigurationEntities(); + return this.smsService.findAllSMSConfigurationEntities(); } @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT) diff --git a/service/src/main/java/org/apache/fineract/cn/notification/service/rest/SMSServiceRestController.java b/service/src/main/java/org/apache/fineract/cn/notification/service/rest/TemplateRestController.java similarity index 63% copy from service/src/main/java/org/apache/fineract/cn/notification/service/rest/SMSServiceRestController.java copy to service/src/main/java/org/apache/fineract/cn/notification/service/rest/TemplateRestController.java index b9a6e83..aed08f2 100644 --- a/service/src/main/java/org/apache/fineract/cn/notification/service/rest/SMSServiceRestController.java +++ b/service/src/main/java/org/apache/fineract/cn/notification/service/rest/TemplateRestController.java @@ -24,11 +24,14 @@ import org.apache.fineract.cn.command.gateway.CommandGateway; import org.apache.fineract.cn.lang.ServiceException; import org.apache.fineract.cn.notification.api.v1.PermittableGroupIds; import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; -import org.apache.fineract.cn.notification.api.v1.domain.SMSConfiguration; +import org.apache.fineract.cn.notification.api.v1.domain.Template; import org.apache.fineract.cn.notification.service.ServiceConstants; -import org.apache.fineract.cn.notification.service.internal.command.*; -import org.apache.fineract.cn.notification.service.internal.service.NotificationService; +import org.apache.fineract.cn.notification.service.internal.command.CreateSMSConfigurationCommand; +import org.apache.fineract.cn.notification.service.internal.command.CreateTemplateCommand; +import org.apache.fineract.cn.notification.service.internal.command.DeleteSMSConfigurationCommand; +import org.apache.fineract.cn.notification.service.internal.command.UpdateSMSConfigurationCommand; import org.apache.fineract.cn.notification.service.internal.service.SMSService; +import org.apache.fineract.cn.notification.service.internal.service.TemplateService; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -42,34 +45,21 @@ import java.util.List; @SuppressWarnings("unused") @RestController -@RequestMapping("/configuration/sms/") -public class SMSServiceRestController { +@RequestMapping("/template") +public class TemplateRestController { private final Logger logger; private final CommandGateway commandGateway; - private final SMSService smsService; + private final TemplateService templateService; @Autowired - public SMSServiceRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger, - final CommandGateway commandGateway, - final SMSService smsService) { + public TemplateRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger, + final CommandGateway commandGateway, + final TemplateService templateService) { super(); this.logger = logger; this.commandGateway = commandGateway; - this.smsService = smsService; - } - - @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT) - @RequestMapping( - value = "/active", - method = RequestMethod.GET, - consumes = MediaType.ALL_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE - ) - public - @ResponseBody - List<SMSConfiguration> findAllActiveSMSConfigurationEntities() { - return this.smsService.findAllActiveSMSConfigurationEntities(); + this.templateService = templateService; } @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT) @@ -81,10 +71,10 @@ public class SMSServiceRestController { ) public @ResponseBody - ResponseEntity<SMSConfiguration> findSMSConfigurationByIdentifier(@PathVariable("identifier") final String identifier) { - return this.smsService.findSMSConfigurationByIdentifier(identifier) + ResponseEntity<Template> findTemplateByIdentifier(@PathVariable("identifier") final String identifier) { + return this.templateService.findTemplateWithIdentifier(identifier) .map(ResponseEntity::ok) - .orElseThrow(() -> ServiceException.notFound("SMS Gateway Configuration with identifier " + identifier + " doesn't exist.")); + .orElseThrow(() -> ServiceException.notFound("Template with identifier " + identifier + " doesn't exist.")); } @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT) @@ -96,12 +86,12 @@ public class SMSServiceRestController { ) public @ResponseBody - ResponseEntity<Void> createSMSConfiguration(@RequestBody @Valid final SMSConfiguration smsConfiguration) throws InterruptedException { - if (this.smsService.smsConfigurationExists(smsConfiguration.getIdentifier())) { - throw ServiceException.conflict("Configuration {0} already exists.", smsConfiguration.getIdentifier()); + ResponseEntity<Void> createTemplate(@RequestBody @Valid final Template template) throws InterruptedException { + if (this.templateService.templateExists(template.getTemplateIdentifier())) { + throw ServiceException.conflict("Template {0} already exists.", template.getTemplateIdentifier()); } - this.commandGateway.process(new CreateSMSConfigurationCommand(smsConfiguration)); + this.commandGateway.process(new CreateTemplateCommand(template)); return new ResponseEntity<>(HttpStatus.CREATED); } @@ -113,8 +103,8 @@ public class SMSServiceRestController { ) public @ResponseBody - ResponseEntity<Void> updateSMSConfiguration(@RequestBody @Valid final SMSConfiguration smsConfiguration) { - this.commandGateway.process(new UpdateSMSConfigurationCommand(smsConfiguration)); + ResponseEntity<Void> updateTemplate(@RequestBody @Valid final Template template) { + this.commandGateway.process(template); return ResponseEntity.accepted().build(); } @@ -126,8 +116,8 @@ public class SMSServiceRestController { ) public @ResponseBody - ResponseEntity<Void> deleteSMSConfiguration(@PathVariable @Valid final String identifier) { - this.commandGateway.process(new DeleteSMSConfigurationCommand(identifier)); + ResponseEntity<Void> deleteTemplate(@PathVariable @Valid final String identifier) { + this.commandGateway.process(identifier); return ResponseEntity.ok().build(); } } diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml index d1c0460..23054cf 100644 --- a/service/src/main/resources/application.yml +++ b/service/src/main/resources/application.yml @@ -85,3 +85,7 @@ flyway: notification: user: operator password: init1@l + +activemq: + brokerUrl: vm://localhost?broker.persistent=false + concurrency: 1-1 diff --git a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql index 9e1aba3..19f43bd 100644 --- a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql +++ b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql @@ -33,7 +33,7 @@ CREATE TABLE wada_sms_gateway_configurations ( -- Table wada_email_gateway_configurations -- ----------------------------------------------------- CREATE TABLE wada_email_gateway_configurations ( - id INT(11) NOT NULL AUTO_INCREMENT, + id INT NOT NULL AUTO_INCREMENT, identifier VARCHAR(45) NULL DEFAULT NULL, host VARCHAR(45) NOT NULL, port VARCHAR(45) NOT NULL, @@ -49,22 +49,14 @@ CREATE TABLE wada_email_gateway_configurations ( -- Table wada_templates -- ----------------------------------------------------- CREATE TABLE wada_templates ( - id INT(11) NOT NULL AUTO_INCREMENT, - identifier VARCHAR(45) NULL DEFAULT NULL, - event VARCHAR(45) NULL DEFAULT NULL, + id INT NOT NULL AUTO_INCREMENT, + template_identifier VARCHAR(45) NULL DEFAULT NULL, + sender_email VARCHAR(255) NULL DEFAULT NULL, + subject VARCHAR(255) NULL DEFAULT NULL, + message VARCHAR(1024) NULL DEFAULT NULL, url VARCHAR(255) NOT NULL, PRIMARY KEY (id)); --- ----------------------------------------------------- --- Table wada_data_source_application --- ----------------------------------------------------- - CREATE TABLE wada_data_source_application ( - id BIGINT NOT NULL AUTO_INCREMENT, - tenant_identifier VARCHAR(32) NOT NULL, - application_identifier VARCHAR(32) NOT NULL, - permittable_identifier VARCHAR(32) NOT NULL, - PRIMARY KEY (id) -); -INSERT INTO wada_sms_gateway_configurations VALUES ('1', 'Twilio', 'ACdc00866577a42133e16d98456ad15592', '0b2f78b1c083eb71599d014d1af5748e', '+12055486680', 'ACTIVE'); -INSERT INTO wada_email_gateway_configurations VALUES ('1', 'Gmail', 'smtp.gmail.com', '587','[email protected]', 'pnuugpwmcibipdpw', 'smtp', 'true', 'true', 'ACTIVE'); +INSERT INTO wada_sms_gateway_configurations VALUES ('1', 'DEFAULT', 'ACdc00866577a42133e16d98456ad15592', '0b2f78b1c083eb71599d014d1af5748e', '+12055486680', 'ACTIVE'); +INSERT INTO wada_email_gateway_configurations VALUES ('1', 'DEFAULT', 'smtp.gmail.com', '587','[email protected]', 'pnuugpwmcibipdpw', 'smtp', 'true', 'true', 'ACTIVE'); diff --git a/service/src/main/resources/templatedetails/templates.csv b/service/src/main/resources/templatedetails/templates.csv new file mode 100644 index 0000000..ddc76ef --- /dev/null +++ b/service/src/main/resources/templatedetails/templates.csv @@ -0,0 +1,28 @@ +- +- Licensed to the Apache Software Foundation (ASF) under one +- or more contributor license agreements. See the NOTICE file +- distributed with this work for additional information +- regarding copyright ownership. The ASF licenses this file +- to you 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. +- +template_identifier,sender_email,subject,message,url +customerCreatedEvent,DEFAULT,Account created,"Your account has been created",template +customerUpdatedEvents,DEFAULT,Account updated,Your account has been Updated,template +customerActivatedEvent,DEFAULT,Account Activated,Your account has been Activated,template +customerLockedEvent,DEFAULT,Account Locked,Your account has been Locked,template +customerUnlockedEvent,DEFAULT,Account unlocked,Your account has been Unlocked,template +customerClosedEvent,DEFAULT,Account closed successfully,Your account has been Closed,template +customerReopenedEvent,DEFAULT,Account Reopened,Your account has been reopened,template +contactDetailsChangedEvent,DEFAULT,Contact details has been updated,Your contact has been changed successfully,template +addressChangedEvent,DEFAULT,Residence address has been changed,Your address has been changed successfully,template diff --git a/service/src/main/resources/templates/template.html b/service/src/main/resources/templates/template.html new file mode 100644 index 0000000..d6a4d1c --- /dev/null +++ b/service/src/main/resources/templates/template.html @@ -0,0 +1,457 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> + +<!doctype html> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:th="http://www.thymeleaf.org"> +<head> + <meta name="viewport" content="width=device-width"/> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>Simple Email</title> + <link href="https://fonts.googleapis.com/css?family=helvatica" rel="stylesheet"/> + <style> + /* ------------------------------------- + GLOBAL RESETS + ------------------------------------- */ + + /*All the styling goes here*/ + + img { + border: none; + -ms-interpolation-mode: bicubic; + max-width: 100%; + } + + body { + background-color: #f6f6f6; + font-family: sans-serif; + -webkit-font-smoothing: antialiased; + font-size: 14px; + line-height: 1.4; + margin: 0; + padding: 0; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + } + + table { + border-collapse: separate; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + width: 100%; + } + + table td { + font-family: sans-serif; + font-size: 14px; + vertical-align: top; + } + + /* ------------------------------------- + CONTAINER + ------------------------------------- */ + + .body { + background-color: #f6f6f6; + width: 100%; + } + + /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */ + .container { + display: block; + margin: 0 auto !important; + /* makes it centered */ + max-width: 580px; + padding: 10px; + width: 580px; + } + + /* This should also be a block element, so that it will fill 100% of the .container */ + .content { + box-sizing: border-box; + display: block; + margin: 0 auto; + max-width: 580px; + padding: 10px; + } + + /* ------------------------------------- + HEADER, FOOTER, MAIN + ------------------------------------- */ + .main { + background: #ffffff; + border-radius: 3px; + width: 100%; + } + + .wrapper { + box-sizing: border-box; + padding: 20px; + } + + .content-block { + padding-bottom: 10px; + padding-top: 10px; + } + + .footer { + clear: both; + margin-top: 10px; + text-align: center; + width: 100%; + } + + .footer td, + .footer p, + .footer span, + .footer a { + color: #999999; + font-size: 12px; + text-align: center; + } + + /* ------------------------------------- + TYPOGRAPHY + ------------------------------------- */ + h1, + h2, + h3, + h4 { + color: #000000; + font-family: sans-serif; + font-weight: 400; + line-height: 1.4; + margin: 0; + margin-bottom: 30px; + } + + h1 { + font-size: 35px; + font-weight: 300; + text-align: center; + text-transform: capitalize; + } + + p, + ul, + ol { + font-family: sans-serif; + font-size: 14px; + font-weight: normal; + margin: 0; + margin-bottom: 15px; + } + + p li, + ul li, + ol li { + list-style-position: inside; + margin-left: 5px; + } + + a { + color: #a92334; + text-decoration: underline; + } + + /* ------------------------------------- + BUTTONS + ------------------------------------- */ + .btn { + box-sizing: border-box; + width: 100%; + } + + .btn > tbody > tr > td { + padding-bottom: 15px; + } + + .btn table { + width: auto; + } + + .btn table td { + background-color: #ffffff; + border-radius: 5px; + text-align: center; + } + + .btn a { + background-color: #ffffff; + border: solid 1px #a92334; + border-radius: 5px; + box-sizing: border-box; + color: #a92334; + cursor: pointer; + display: inline-block; + font-size: 14px; + font-weight: bold; + margin: 0; + padding: 12px 25px; + text-decoration: none; + text-transform: capitalize; + } + + .btn-primary table td { + background-color: #a92334; + } + + .btn-primary a { + background-color: #a92334; + border-color: #a92334; + color: #ffffff; + } + + /* ------------------------------------- + OTHER STYLES THAT MIGHT BE USEFUL + ------------------------------------- */ + .last { + margin-bottom: 0; + } + + .first { + margin-top: 0; + } + + .align-center { + text-align: center; + } + + .align-right { + text-align: right; + } + + .align-left { + text-align: left; + } + + .clear { + clear: both; + } + + .mt0 { + margin-top: 0; + } + + .mb0 { + margin-bottom: 0; + } + + .preheader { + color: transparent; + display: none; + height: 0; + max-height: 0; + max-width: 0; + opacity: 0; + overflow: hidden; + mso-hide: all; + visibility: hidden; + width: 0; + } + + .powered-by a { + text-decoration: none; + } + + hr { + border: 0; + border-bottom: 1px solid #f6f6f6; + margin: 20px 0; + } + + /* ------------------------------------- + RESPONSIVE AND MOBILE FRIENDLY STYLES + ------------------------------------- */ + @media only screen and (max-width: 620px) { + table[class=body] h1 { + font-size: 28px !important; + margin-bottom: 10px !important; + } + + table[class=body] p, + table[class=body] ul, + table[class=body] ol, + table[class=body] td, + table[class=body] span, + table[class=body] a { + font-size: 16px !important; + } + + table[class=body] .wrapper, + table[class=body] .article { + padding: 10px !important; + } + + table[class=body] .content { + padding: 0 !important; + } + + table[class=body] .container { + padding: 0 !important; + width: 100% !important; + } + + table[class=body] .main { + border-left-width: 0 !important; + border-radius: 0 !important; + border-right-width: 0 !important; + } + + table[class=body] .btn table { + width: 100% !important; + } + + table[class=body] .btn a { + width: 100% !important; + } + + table[class=body] .img-responsive { + height: auto !important; + max-width: 100% !important; + width: auto !important; + } + } + + /* ------------------------------------- + PRESERVE THESE STYLES IN THE HEAD + ------------------------------------- */ + @media all { + .headTab{ + height: 60px; + width: auto; + max-width: 540px; + padding: 10px; + margin: 0 auto; + background-color: #ffffff; + /*-webkit-box-shadow: 0px -1px 139px -25px rgba(0,0,0,0.69); + -moz-box-shadow: 0px -1px 139px -25px rgba(0,0,0,0.69); + box-shadow: 0px -1px 139px -25px rgba(0,0,0,0.69);*/ + } + .ExternalClass { + width: 100%; + } + + .ExternalClass, + .ExternalClass p, + .ExternalClass span, + .ExternalClass font, + .ExternalClass td, + .ExternalClass div { + line-height: 100%; + } + + .apple-link a { + color: inherit !important; + font-family: inherit !important; + font-size: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + text-decoration: none !important; + } + + .btn-primary table td:hover { + background-color: #a92334 !important; + } + + .btn-primary a:hover { + background-color: #a92334 !important; + border-color: #a92334 !important; + } + } + + </style> +</head> +<body> +<span class="preheader">Header Message</span> +<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body"> + + <tr> + <td> </td> + + <td class="container"> + <div class="headTab"><img style="height:40px; margin:10px;" class="img-responsive" src="http://fineract.apache.org/images/apache-fineract-logo.png" alt="Logo"></div> + + <div class="content"> + + <!-- START CENTERED WHITE CONTAINER --> + <table role="presentation" class="main"> + + <!-- START MAIN CONTENT AREA --> + <tr> + <td class="wrapper"> + <table role="presentation" border="0" cellpadding="0" cellspacing="0"> + <tr> + <td> + <p>Dear Valued Customer,</p> + + <p>This is a sample message an account update</p> + <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary"> + <tbody> + <tr> + <td align="left"> + <table role="presentation" border="0" cellpadding="0" cellspacing="0"> + <tbody> + <tr> + <td> <a href="http://fineract.apache.org" target="_blank">Call To Action</a> </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + <p>No reply required. This is a computer generated mail.</p> + <p>Best Regards,</p> + <p>MFI</p> + </td> + </tr> + </table> + </td> + </tr> + + <!-- END MAIN CONTENT AREA --> + </table> + <!-- END CENTERED WHITE CONTAINER --> + + <!-- START FOOTER --> + <div class="footer"> + <table role="presentation" border="0" cellpadding="0" cellspacing="0"> + <tr> + <td class="content-block"> + <span class="apple-link">Apache Software Foundation, Forest Hill, Maryland, United States</span> + <p> Don't like these emails? <a href="">Unsubscribe</a>.</p> + </td> + </tr> + <tr> + <td class="content-block powered-by"> + Powered by <a href="http://fineract.apache.org">Apache Fineract</a>. + </td> + </tr> + </table> + </div> + <!-- END FOOTER --> + + </div> + </td> + <td> </td> + </tr> +</table> +</body> +</html> diff --git a/shared.gradle b/shared.gradle index c270323..7ecfdc6 100644 --- a/shared.gradle +++ b/shared.gradle @@ -39,7 +39,9 @@ ext.versions = [ validator : '5.3.0.Final', springjavamail : '1.4.1.RELEASE', twilioapi : '7.17.+', - junit : '4.12' + junit : '4.12', + apachecsvreader : '1.4' + ] apply plugin: 'java'
