diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index bc0c43e2d..9a87171cc 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -2629,6 +2629,30 @@
|
+ | Two-Factor Authentication |
+ twofactor |
+ Request OTP |
+ List OTP delivery methods |
+ |
+ |
+
+
+ |
+ twofactor/validate |
+ Validate OTP |
+ |
+ |
+ |
+
+
+ |
+ twofactor/invalidate |
+ Invalidate Access Token |
+ |
+ |
+ |
+
+
| User |
users |
Create a User |
@@ -2942,6 +2966,16 @@
external service Configuration
|
+
+ | Two-Factor Configuration |
+ twofactor/configure |
+ |
+ Retrieve
+ Two-Factor Configuration |
+ Update
+ Two-Factor Configuration |
+ |
+
@@ -3930,6 +3964,11 @@
Default authentication is using HTTP Basic Auth. Oauth2 can be enabled by using -Psecurity=oauth option on gradle build command , refer
the platform setup wiki for additional details.
+
+ Optionally, two-factor authentication can be enabled by using
+ -Ptwofactor=enabled on gradle build.
+ Details of the authentication workflow with two-factor authentication enabled can be found here.
+
The platform has been configured to reject plain HTTP requests and
to expect all API requests to be made over
+
+
+
+
Two-Factor Authentication
+
+ Two-Factor authentication is supported by requesting & verifying
+ one-time passwords(OTP). OTPs are sent via SMS & email.
+
+
+ By default, two-factor authentication is disabled by default.
+ More information on how to enable TFA can be found here.
+
+
+ Two-factor authentication workflow:
+
+ - User authticates via BasicAuth / oAauth
+ - Client requests a list of supported OTP delivery methods for the authenticated user(Get Delivery Methods)
+ - User selects an OTP delivery method and client sends a request for OTP(Request OTP)
+ - User receives an OTP and the client sends it for verification(Validate OTP)
+ - If the OTP is valid, an access token is returned
+ - The access token is sent in following requestes to the server as a header Fineract-Platform-TFA-Token
+ - On session end, the access token should be invalidatedInvalidate Access Token)
+
+
+
+ Two-Factor authentication and delivery methods can be configured via
+ the /twofactor/configure endpoint.
+
+
+
+
+
+
+
+
+
+
+
Get Delivery Methods
+
Returns a list of possible OTP delivery methods for the current user
+
Requires first-factor authenticated user.
+
+
+ GET https://DomainName/api/v1/twofactor
+
+[
+ {
+ "name": "sms",
+ "target": "08888888888"
+ },
+ {
+ "name": "email",
+ "target": "user@example.com"
+ }
+]
+
+
+
+
+
+
+
Request OTP
+
Requests an OTP.
+
Requires first-factor authenticated user.
+
Arguments
+
+ - deliveryMethod
+ -
+ String mandatory, the delivery method name
+
+ - extendedToken
+ -
+ boolean optional, whether to request an extended token, default false
+
+
+
+
+ POST https://DomainName/api/v1/twofactor?deliveryMethod=sms&extendedToken=false
+
+{
+ "requestTime": 1500000000000,
+ "tokenLiveTimeInSec": 300,
+ "extendedAccessToken": false,
+ "deliveryMethod": {
+ "name": "sms",
+ "target": "08888888888"
+ }
+}
+
+
+
+
+
+
+
Validate OTP
+
Validates an OTP. If the OTP is valid, an access token is created.
+
The returned access token is later sent as a header Fineract-Platform-TFA-Token.
+
Requires first-factor authenticated user.
+
Arguments
+
+ - token
+ -
+ String mandatory, the OTP to validate
+
+
+
+
+ POST https://DomainName/api/v1/twofactor/validate?token=YYYYY
+
+{
+ "token": "cb0bb6e33fc540709d50a16eb2e555f9",
+ "validFrom": 1501530702801,
+ "validTo": 1501617102801
+}
+
+
+
+
+
+
+
Invalidate Access Token
+
Invalidates an access token.
+
Two factor access tokens should be invalidated on logout.
+
Requires fully authenticated user.
+
+
+ POST https://DomainName/api/v1/twofactor/invalidate
+
+{
+ "token": "cb0bb6e33fc540709d50a16eb2e555f9"
+}
+
+{
+ "resourceIdentifier": "cb0bb6e33fc540709d50a16eb2e555f9"
+}
+
+
@@ -19391,6 +19566,73 @@ Content-Type: application/json
+
+
+
+
Two-Factor Configuration
+
The following section describes the way to configure two-factor authentication
+
Two-Factor Authentication has to be enabled by either building with Gradle arguments
+ -Ptwofactor=enabled or enabling the twofactor profile via env. variable
+
+
In order for SMS to be enabled an SMS bridge has to be setup with the message-gateway service.
+
+
+
+
+
+
+
+
+
Retrieve Two-Factor Configuration
+
Returns available two-factor configuration.
+
+
+ GET https://DomainName/api/v1/twofactor/configure
+
+
+{
+ "otp-delivery-email-body": "Hello {{username}}.\n\nYour OTP login token is {{token}}.",
+ "otp-delivery-sms-enable": true,
+ "otp-delivery-sms-provider": 6,
+ "otp-delivery-email-subject": "Fineract Two-Factor Authentication Token",
+ "otp-token-length": 5,
+ "access-token-live-time-extended": 604800,
+ "otp-delivery-email-enable": true,
+ "otp-token-live-time": 300,
+ "otp-delivery-sms-text": "Your authentication token for Fineract is {{token}}.",
+ "access-token-live-time": 86400
+}
+
+
+
+
+
+
+
+
Update Two-Factor Configuration
+
Update two-factor configuration.
+
+
+ PUT https://DomainName/api/v1/twofactor/configure
+
+
+{
+ "otp-delivery-sms-provider": 7
+ "otp-delivery-sms-enable": false
+}
+
+
+{
+ "changes": {
+ "otp-delivery-sms-enable": false,
+ "otp-delivery-sms-provider": 7
+ }
+}
+
+
+
+
diff --git a/api-docs/apidocs.css b/api-docs/apidocs.css
index f8816a7de..21bc11c27 100644
--- a/api-docs/apidocs.css
+++ b/api-docs/apidocs.css
@@ -745,4 +745,8 @@ ul.field li {
tt {
font-size: 9.5pt;
+}
+
+ol.normalli li {
+ list-style-type: decimal;
}
\ No newline at end of file
diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle
index cf6febab2..2ddf09c36 100644
--- a/fineract-provider/build.gradle
+++ b/fineract-provider/build.gradle
@@ -195,17 +195,33 @@ if (project.hasProperty('env') && project.getProperty('env') == 'dev') {
/* Enable Oauth2 authentication based on environment, default to HTTP basic auth */
if (project.hasProperty('security') && project.getProperty('security') == 'oauth') {
- copy {
- from './properties/oauth/'
- into 'src/main/resources/'
- include '*.properties'
- }
+ if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') {
+ copy {
+ from './properties/oauth/twofactor/'
+ into 'src/main/resources/'
+ include '*.properties'
+ }
+ } else {
+ copy {
+ from './properties/oauth/'
+ into 'src/main/resources/'
+ include '*.properties'
+ }
+ }
} else {
- copy {
- from './properties/basicauth/'
- into 'src/main/resources/'
- include '*.properties'
- }
+ if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') {
+ copy {
+ from './properties/basicauth/twofactor/'
+ into 'src/main/resources/'
+ include '*.properties'
+ }
+ } else {
+ copy {
+ from './properties/basicauth/'
+ into 'src/main/resources/'
+ include '*.properties'
+ }
+ }
}
task dist(type:Zip){
diff --git a/fineract-provider/properties/basicauth/twofactor/application.properties b/fineract-provider/properties/basicauth/twofactor/application.properties
new file mode 100644
index 000000000..5b4d49668
--- /dev/null
+++ b/fineract-provider/properties/basicauth/twofactor/application.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+spring.profiles.default=basicauth
+spring.profiles.active=basicauth,twofactor
\ No newline at end of file
diff --git a/fineract-provider/properties/oauth/twofactor/application.properties b/fineract-provider/properties/oauth/twofactor/application.properties
new file mode 100644
index 000000000..12cbfbe80
--- /dev/null
+++ b/fineract-provider/properties/oauth/twofactor/application.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+spring.profiles.default=basicauth
+spring.profiles.active=oauth,twofactor
\ No newline at end of file
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HolidayHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HolidayHelper.java
index d8b90adfd..b1a5e64bd 100644
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HolidayHelper.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/HolidayHelper.java
@@ -59,7 +59,7 @@ public class HolidayHelper {
map.put("fromDate", "01 April 2013");
map.put("toDate", "01 April 2013");
map.put("repaymentsRescheduledTo", "08 April 2013");
-
+ map.put("reschedulingType", 2);
String HolidayCreateJson = new Gson().toJson(map);
System.out.println(HolidayCreateJson);
return HolidayCreateJson;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 0ad36120c..ae53bb78c 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -2929,7 +2929,7 @@ public class CommandWrapperBuilder {
this.href = "/smscampaigns/"+resourceId;
return this;
}
-
+
public CommandWrapperBuilder holdAmount(final Long accountId) {
this.actionName = "HOLDAMOUNT";
this.entityName = "SAVINGSACCOUNT";
@@ -3039,6 +3039,97 @@ public class CommandWrapperBuilder {
this.entityId = adHocId;
this.href = "/adhocquery/" + adHocId;
this.json = "{}";
+ return this ;
+ }
+ public CommandWrapperBuilder createEmail() {
+ this.actionName = "CREATE";
+ this.entityName = "EMAIL";
+ this.entityId = null;
+ this.href = "/emailcampaigns/template";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateEmail(final Long resourceId) {
+ this.actionName = "UPDATE";
+ this.entityName = "EMAIL";
+ this.entityId = resourceId;
+ this.href = "/emailcampaigns/"+resourceId;
+ return this;
+ }
+
+ public CommandWrapperBuilder deleteEmail(final Long resourceId) {
+ this.actionName = "DELETE";
+ this.entityName = "EMAIL";
+ this.entityId = resourceId;
+ this.href = "/emailcampaigns/"+resourceId;
+ return this;
+ }
+
+ public CommandWrapperBuilder createEmailCampaign() {
+ this.actionName = "CREATE";
+ this.entityName = "EMAIL_CAMPAIGN";
+ this.entityId = null;
+ this.href = "/emailcampaigns/campaign";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateEmailCampaign(final Long resourceId) {
+ this.actionName = "UPDATE";
+ this.entityName = "EMAIL_CAMPAIGN";
+ this.entityId = resourceId;
+ this.href = "/emailcampaigns/"+resourceId;
+ return this;
+ }
+
+ public CommandWrapperBuilder deleteEmailCampaign(final Long resourceId) {
+ this.actionName = "DELETE";
+ this.entityName = "EMAIL_CAMPAIGN";
+ this.entityId = resourceId;
+ this.href = "/emailcampaigns/"+resourceId;
+ return this;
+ }
+
+ public CommandWrapperBuilder activateEmailCampaign(final Long resourceId) {
+ this.actionName = "ACTIVATE";
+ this.entityName = "EMAIL_CAMPAIGN";
+ this.entityId = resourceId;
+ this.href = "/emailcampaigns/"+resourceId + "?command=activate";
+ return this;
+ }
+
+ public CommandWrapperBuilder closeEmailCampaign(final Long resourceId) {
+ this.actionName = "CLOSE";
+ this.entityName = "EMAIL_CAMPAIGN";
+ this.entityId = resourceId;
+ this.href = "/emailcampaigns/"+resourceId + "?command=close";
+ return this;
+ }
+ public CommandWrapperBuilder reactivateEmailCampaign(final Long resourceId) {
+ this.actionName = "REACTIVATE";
+ this.entityName = "EMAIL_CAMPAIGN";
+ this.entityId = resourceId;
+ this.href = "/emailcampaigns/"+resourceId + "?command=reactivate";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateEmailConfiguration() {
+ this.actionName = "UPDATE";
+ this.entityName = "EMAIL_CONFIGURATION";
+ this.href = "/emailcampaigns/configuration/";
+ return this;
+ }
+
+ public CommandWrapperBuilder invalidateTwoFactorAccessToken() {
+ this.actionName = "INVALIDATE";
+ this.entityName = "TWOFACTOR_ACCESSTOKEN";
+ this.href = "/twofactor/invalidate";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateTwoFactorConfiguration() {
+ this.actionName = "UPDATE";
+ this.entityName = "TWOFACTOR_CONFIGURATION";
+ this.href = "/twofactor/configure";
return this;
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java
index b6a0aaf7c..421882a34 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java
@@ -21,7 +21,7 @@ package org.apache.fineract.infrastructure.campaigns.constants;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public enum CampaignType {
- INVALID(0, "campaignType.invalid"), SMS(1, "campaignType.sms");
+ INVALID(0, "campaignType.invalid"), SMS(1, "campaignType.sms"), NOTIFICATION(2, "campaignType.notification");
private Integer value;
private String code;
@@ -48,6 +48,9 @@ public enum CampaignType {
case 1:
type = SMS;
break;
+ case 2:
+ type = NOTIFICATION;
+ break;
}
return type;
}
@@ -66,6 +69,9 @@ public enum CampaignType {
case SMS:
optionData = new EnumOptionData(CampaignType.SMS.getValue().longValue(), CampaignType.SMS.getCode(), "SMS");
break;
+ case NOTIFICATION:
+ optionData = new EnumOptionData(CampaignType.NOTIFICATION.getValue().longValue(), CampaignType.NOTIFICATION.getCode(), "NOTIFICATION");
+ break;
}
return optionData;
}
@@ -73,4 +79,8 @@ public enum CampaignType {
public boolean isSms() {
return this.value.equals(CampaignType.SMS.getValue());
}
+
+ public boolean isNotificaion() {
+ return this.value.equals(CampaignType.NOTIFICATION.getValue());
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/EmailApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/EmailApiConstants.java
new file mode 100644
index 000000000..1307467ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/EmailApiConstants.java
@@ -0,0 +1,54 @@
+/**
+ * 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.infrastructure.campaigns.email;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class EmailApiConstants {
+
+ public static final String RESOURCE_NAME = "email";
+
+ // general
+ public static final String localeParamName = "locale";
+ public static final String dateFormatParamName = "dateFormat";
+
+ // request parameters
+ public static final String idParamName = "id";
+ public static final String groupIdParamName = "groupId";
+ public static final String clientIdParamName = "clientId";
+ public static final String staffIdParamName = "staffId";
+ public static final String subjectParamName = "emailSubject";
+ public static final String messageParamName = "emailMessage";
+
+ // response parameters
+ public static final String statusParamName = "status";
+
+ public static final Set
CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+ dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName));
+
+ public static final Set UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName));
+
+ public static final String SMTP_SERVER = "SMTP_SERVER";
+ public static final String SMTP_PORT = "SMTP_PORT";
+ public static final String SMTP_USERNAME = "SMTP_USERNAME";
+ public static final String SMTP_PASSWORD = "SMTP_PASSWORD";
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/ScheduledEmailConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/ScheduledEmailConstants.java
new file mode 100644
index 000000000..950f232f0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/ScheduledEmailConstants.java
@@ -0,0 +1,82 @@
+/**
+ * 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.infrastructure.campaigns.email;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ScheduledEmailConstants {
+
+ // define the API resource entity name
+ public static final String SCHEDULED_EMAIL_ENTITY_NAME = "SCHEDULEDEMAIL";
+
+ // general API resource request parameter constants
+ public static final String LOCALE_PARAM_NAME = "locale";
+ public static final String DATE_FORMAT_PARAM_NAME = "dateFormat";
+
+ // parameter constants for create/update entity API request
+ public static final String NAME_PARAM_NAME = "name";
+ public static final String DESCRIPTION_PARAM_NAME = "description";
+ public static final String START_DATE_TIME_PARAM_NAME = "startDateTime";
+ public static final String RECURRENCE_PARAM_NAME = "recurrence";
+ public static final String EMAIL_RECIPIENTS_PARAM_NAME = "emailRecipients";
+ public static final String EMAIL_SUBJECT_PARAM_NAME = "emailSubject";
+ public static final String EMAIL_MESSAGE_PARAM_NAME = "emailMessage";
+ public static final String EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME = "emailAttachmentFileFormatId";
+ public static final String STRETCHY_REPORT_ID_PARAM_NAME = "stretchyReportId";
+ public static final String STRETCHY_REPORT_PARAM_MAP_PARAM_NAME = "stretchyReportParamMap";
+ public static final String IS_ACTIVE_PARAM_NAME = "isActive";
+
+ // other parameter constants
+ public static final String ID_PARAM_NAME = "id";
+ public static final String EMAIL_ATTACHMENT_FILE_FORMAT_PARAM_NAME = "emailAttachmentFileFormat";
+ public static final String PREVIOUS_RUN_DATE_TIME_PARAM_NAME = "previousRunDateTime";
+ public static final String NEXT_RUN_DATE_TIME_PARAM_NAME = "nextRunDateTime";
+ public static final String PREVIOUS_RUN_STATUS = "previousRunStatus";
+ public static final String PREVIOUS_RUN_ERROR_LOG = "previousRunErrorLog";
+ public static final String PREVIOUS_RUN_ERROR_MESSAGE = "previousRunErrorMessage";
+ public static final String NUMBER_OF_RUNS = "numberOfRuns";
+ public static final String STRETCHY_REPORT_PARAM_NAME = "stretchyReport";
+
+ // list of permitted parameters for the create report mailing job request
+ public static final Set CREATE_REQUEST_PARAMETERS = new HashSet<>(Arrays.asList(LOCALE_PARAM_NAME, DATE_FORMAT_PARAM_NAME,
+ NAME_PARAM_NAME, DESCRIPTION_PARAM_NAME, RECURRENCE_PARAM_NAME, EMAIL_RECIPIENTS_PARAM_NAME, EMAIL_SUBJECT_PARAM_NAME,
+ EMAIL_MESSAGE_PARAM_NAME, EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, STRETCHY_REPORT_ID_PARAM_NAME,
+ STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, IS_ACTIVE_PARAM_NAME, START_DATE_TIME_PARAM_NAME));
+
+ // list of permitted parameters for the update report mailing job request
+ public static final Set UPDATE_REQUEST_PARAMETERS = new HashSet<>(Arrays.asList(LOCALE_PARAM_NAME, DATE_FORMAT_PARAM_NAME,
+ NAME_PARAM_NAME, DESCRIPTION_PARAM_NAME, RECURRENCE_PARAM_NAME, EMAIL_RECIPIENTS_PARAM_NAME, EMAIL_SUBJECT_PARAM_NAME,
+ EMAIL_MESSAGE_PARAM_NAME, EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, STRETCHY_REPORT_ID_PARAM_NAME,
+ STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, IS_ACTIVE_PARAM_NAME, START_DATE_TIME_PARAM_NAME));
+
+ // list of parameters that represent the properties of a report mailing job
+ public static final Set REPORT_MAILING_JOB_DATA_PARAMETERS = new HashSet<>(Arrays.asList(ID_PARAM_NAME, NAME_PARAM_NAME,
+ DESCRIPTION_PARAM_NAME, RECURRENCE_PARAM_NAME, EMAIL_RECIPIENTS_PARAM_NAME, EMAIL_SUBJECT_PARAM_NAME, EMAIL_MESSAGE_PARAM_NAME,
+ EMAIL_ATTACHMENT_FILE_FORMAT_PARAM_NAME, STRETCHY_REPORT_PARAM_NAME, STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, IS_ACTIVE_PARAM_NAME,
+ START_DATE_TIME_PARAM_NAME, PREVIOUS_RUN_DATE_TIME_PARAM_NAME, NEXT_RUN_DATE_TIME_PARAM_NAME, PREVIOUS_RUN_STATUS,
+ PREVIOUS_RUN_ERROR_LOG, PREVIOUS_RUN_ERROR_MESSAGE, NUMBER_OF_RUNS));
+
+ // report mailing job configuration names
+ public static final String GMAIL_SMTP_SERVER = "GMAIL_SMTP_SERVER";
+ public static final String GMAIL_SMTP_PORT = "GMAIL_SMTP_PORT";
+ public static final String GMAIL_SMTP_USERNAME = "GMAIL_SMTP_USERNAME";
+ public static final String GMAIL_SMTP_PASSWORD = "GMAIL_SMTP_PASSWORD";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java
new file mode 100644
index 000000000..6bdae7b62
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailApiResource.java
@@ -0,0 +1,185 @@
+/**
+ * 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.infrastructure.campaigns.email.api;
+
+import org.apache.fineract.accounting.journalentry.api.DateParam;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailData;
+import org.apache.fineract.infrastructure.campaigns.email.service.EmailReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import java.util.Collection;
+import java.util.Date;
+
+@Path("/email")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class EmailApiResource {
+
+ private final String resourceNameForPermissions = "Email";
+ private final PlatformSecurityContext context;
+ private final EmailReadPlatformService readPlatformService;
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+ @Autowired
+ public EmailApiResource(final PlatformSecurityContext context, final EmailReadPlatformService readPlatformService,
+ final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+ this.context = context;
+ this.readPlatformService = readPlatformService;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ }
+
+ @GET
+ public String retrieveAllEmails(@Context final UriInfo uriInfo) {
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+ final Collection emailMessages = this.readPlatformService.retrieveAll();
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, emailMessages);
+ }
+
+ @GET
+ @Path("pendingEmail")
+ public String retrievePendingEmail(@QueryParam("sqlSearch") final String sqlSearch, @QueryParam("offset") final Integer offset,
+ @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+ @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+ final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
+ Collection emailMessages = this.readPlatformService.retrieveAllPending(searchParameters);
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, emailMessages);
+ }
+
+ @GET
+ @Path("sentEmail")
+ public String retrieveSentEmail(@QueryParam("sqlSearch") final String sqlSearch, @QueryParam("offset") final Integer offset,
+ @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+ @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
+ Collection emailMessages = this.readPlatformService.retrieveAllSent(searchParameters);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, emailMessages);
+ }
+
+ @GET
+ @Path("messageByStatus")
+ public String retrieveAllEmailByStatus(@QueryParam("sqlSearch") final String sqlSearch, @QueryParam("offset") final Integer offset,
+ @QueryParam("limit") final Integer limit, @QueryParam("status") final Integer status,
+ @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder,
+ @QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam,
+ @QueryParam("locale") final String locale, @QueryParam("dateFormat") final String dateFormat, @Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+ Date fromDate = null;
+ if (fromDateParam != null) {
+ fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
+ }
+ Date toDate = null;
+ if (toDateParam != null) {
+ toDate = toDateParam.getDate("toDate", dateFormat, locale);
+ }
+ Page emailMessages = this.readPlatformService.retrieveEmailByStatus(limit, status, fromDate, toDate);
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, emailMessages);
+ }
+
+ @GET
+ @Path("failedEmail")
+ public String retrieveFailedEmail(@QueryParam("sqlSearch") final String sqlSearch, @QueryParam("offset") final Integer offset,
+ @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+ @QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
+ Collection emailMessages = this.readPlatformService.retrieveAllFailed(searchParameters);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, emailMessages);
+ }
+
+ @POST
+ public String create(final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().createEmail().withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @GET
+ @Path("{resourceId}")
+ public String retrieveOne(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
+
+ final EmailData emailMessage = this.readPlatformService.retrieveOne(resourceId);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, emailMessage);
+ }
+
+ @PUT
+ @Path("{resourceId}")
+ public String update(@PathParam("resourceId") final Long resourceId, final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmail(resourceId).withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @DELETE
+ @Path("{resourceId}")
+ public String delete(@PathParam("resourceId") final Long resourceId) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteEmail(resourceId).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailCampaignApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailCampaignApiResource.java
new file mode 100644
index 000000000..243eefc86
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailCampaignApiResource.java
@@ -0,0 +1,230 @@
+/**
+ * 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.infrastructure.campaigns.email.api;
+
+import com.google.gson.JsonElement;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailBusinessRulesData;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignData;
+import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignReadPlatformService;
+import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: andrew
+ * Date: 19-5-14
+ * Time: 15:17
+ * To change this template use File | Settings | File Templates.
+ */
+@Path("/email/campaign")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class EmailCampaignApiResource {
+
+
+ //change name to email campaign
+ private final String resourceNameForPermissions = "EMAIL_CAMPAIGN";
+
+ private final PlatformSecurityContext context;
+
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
+
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+ private final EmailCampaignReadPlatformService emailCampaignReadPlatformService;
+ private final FromJsonHelper fromJsonHelper;
+
+
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private final DefaultToApiJsonSerializer emailCampaignDataDefaultToApiJsonSerializer;
+ private final EmailCampaignWritePlatformService emailCampaignWritePlatformService;
+
+ private final DefaultToApiJsonSerializer previewCampaignMessageDefaultToApiJsonSerializer;
+
+
+ @Autowired
+ public EmailCampaignApiResource(final PlatformSecurityContext context,final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
+ final EmailCampaignReadPlatformService emailCampaignReadPlatformService, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+ final DefaultToApiJsonSerializer emailCampaignDataDefaultToApiJsonSerializer,
+ final FromJsonHelper fromJsonHelper, final EmailCampaignWritePlatformService emailCampaignWritePlatformService,
+ final DefaultToApiJsonSerializer previewCampaignMessageDefaultToApiJsonSerializer) {
+ this.context = context;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.emailCampaignReadPlatformService = emailCampaignReadPlatformService;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ this.emailCampaignDataDefaultToApiJsonSerializer = emailCampaignDataDefaultToApiJsonSerializer;
+ this.fromJsonHelper = fromJsonHelper;
+ this.emailCampaignWritePlatformService = emailCampaignWritePlatformService;
+ this.previewCampaignMessageDefaultToApiJsonSerializer = previewCampaignMessageDefaultToApiJsonSerializer;
+ }
+
+
+ @GET
+ @Path("{resourceId}")
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveOneCampaign(@PathParam("resourceId") final Long resourceId,@Context final UriInfo uriInfo){
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ EmailCampaignData emailCampaignData = this.emailCampaignReadPlatformService.retrieveOne(resourceId);
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.emailCampaignDataDefaultToApiJsonSerializer.serialize(settings,emailCampaignData);
+
+ }
+
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAllCampaign(@Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final Collection emailCampaignDataCollection = this.emailCampaignReadPlatformService.retrieveAllCampaign();
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.emailCampaignDataDefaultToApiJsonSerializer.serialize(settings,emailCampaignDataCollection);
+ }
+
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String createCampaign(final String apiRequestBodyAsJson,@Context final UriInfo uriInfo){
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().createEmailCampaign().withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @PUT
+ @Path("{resourceId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String updateCampaign(@PathParam("resourceId") final Long campaignId,final String apiRequestBodyAsJson,@Context final UriInfo uriInfo){
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmailCampaign(campaignId).withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @POST
+ @Path("{resourceId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String activate(@PathParam("resourceId") final Long campaignId, @QueryParam("command") final String commandParam,
+ final String apiRequestBodyAsJson){
+ final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+ CommandProcessingResult result = null;
+ CommandWrapper commandRequest = null;
+ if (is(commandParam, "activate")) {
+ commandRequest = builder.activateEmailCampaign(campaignId).build();
+ result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ }else if (is(commandParam, "close")){
+ commandRequest = builder.closeEmailCampaign(campaignId).build();
+ result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ }else if (is(commandParam, "reactivate")){
+ commandRequest = builder.reactivateEmailCampaign(campaignId).build();
+ result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ }
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @POST
+ @Path("preview")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String preview(final String apiRequestBodyAsJson,@Context final UriInfo uriInfo){
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ PreviewCampaignMessage campaignMessage = null;
+ final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
+ final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
+ campaignMessage = this.emailCampaignWritePlatformService.previewMessage(query);
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings,campaignMessage, new HashSet());
+
+ }
+
+
+ @GET()
+ @Path("template")
+ public String template(@Context final UriInfo uriInfo){
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final Collection emailBusinessRulesDataCollection = this.emailCampaignReadPlatformService.retrieveAll();
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings,emailBusinessRulesDataCollection);
+ }
+
+ @GET
+ @Path("template/{resourceId}")
+ public String retrieveOneTemplate(@PathParam("resourceId") final Long resourceId,@Context final UriInfo uriInfo){
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final EmailBusinessRulesData emailBusinessRulesData = this.emailCampaignReadPlatformService.retrieveOneTemplate(resourceId);
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings,emailBusinessRulesData);
+
+ }
+
+ @DELETE
+ @Path("{resourceId}")
+ public String delete(@PathParam("resourceId") final Long resourceId) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteEmailCampaign(resourceId).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ private boolean is(final String commandParam, final String commandValue) {
+ return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailConfigurationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailConfigurationApiResource.java
new file mode 100644
index 000000000..e95669401
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/api/EmailConfigurationApiResource.java
@@ -0,0 +1,94 @@
+/**
+ * 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.infrastructure.campaigns.email.api;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailConfigurationData;
+import org.apache.fineract.infrastructure.campaigns.email.data.EmailData;
+import org.apache.fineract.infrastructure.campaigns.email.service.EmailConfigurationReadPlatformService;
+import org.apache.fineract.infrastructure.campaigns.email.service.EmailReadPlatformService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import java.util.Collection;
+
+
+@Path("/email/configuration")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Component
+@Scope("singleton")
+public class EmailConfigurationApiResource {
+
+ private final String resourceNameForPermissions = "EMAIL_CONFIGURATION";
+ private final PlatformSecurityContext context;
+ private final EmailReadPlatformService readPlatformService;
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private final EmailConfigurationReadPlatformService emailConfigurationReadPlatformService;
+
+ @Autowired
+ public EmailConfigurationApiResource(final PlatformSecurityContext context, final EmailReadPlatformService readPlatformService,
+ final DefaultToApiJsonSerializer toApiJsonSerializer,
+ final ApiRequestParameterHelper apiRequestParameterHelper,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+ final EmailConfigurationReadPlatformService emailConfigurationReadPlatformService) {
+ this.context = context;
+ this.readPlatformService = readPlatformService;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ this.emailConfigurationReadPlatformService = emailConfigurationReadPlatformService;
+ }
+
+ @GET
+ public String retrieveAll(@Context final UriInfo uriInfo){
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final Collection configuration = this.emailConfigurationReadPlatformService.retrieveAll();
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+ return this.toApiJsonSerializer.serialize(settings, configuration);
+ }
+
+
+ @PUT
+ public String updateConfiguration(@Context final UriInfo uriInfo, final String apiRequestBodyAsJson){
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmailConfiguration().withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailBusinessRulesData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailBusinessRulesData.java
new file mode 100644
index 000000000..2d7dfb15e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailBusinessRulesData.java
@@ -0,0 +1,96 @@
+/**
+ * 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.infrastructure.campaigns.email.data;
+
+import java.util.Map;
+
+public class EmailBusinessRulesData {
+
+ @SuppressWarnings("unused")
+ private final Long reportId;
+
+ @SuppressWarnings("unused")
+ private final String reportName;
+
+ @SuppressWarnings("unused")
+ private final String reportType;
+
+ @SuppressWarnings("unused")
+ private final String reportSubType;
+
+ @SuppressWarnings("unused")
+ private final String reportDescription;
+
+ @SuppressWarnings("unused")
+ private final Map reportParamName;
+
+
+
+ public EmailBusinessRulesData(final Long reportId, final String reportName,final String reportType, final Map reportParamName,
+ final String reportSubType, final String reportDescription) {
+ this.reportId = reportId;
+ this.reportName = reportName;
+ this.reportType = reportType;
+ this.reportParamName = reportParamName;
+ this.reportDescription = reportDescription;
+ this.reportSubType = reportSubType;
+ }
+
+
+ public static EmailBusinessRulesData instance(final Long reportId, final String reportName, final String reportType, final Map reportParamName,
+ final String reportSubType, final String reportDescription){
+ return new EmailBusinessRulesData(reportId,reportName,reportType,reportParamName,reportSubType,reportDescription);
+ }
+
+ public Map getReportParamName() {
+ return reportParamName;
+ }
+
+ public String getReportType() {
+ return reportType;
+ }
+
+ public String getReportName() {
+ return reportName;
+ }
+
+ public Long getReportId() {
+ return reportId;
+ }
+
+ public String getReportDescription() {
+ return reportDescription;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ EmailBusinessRulesData that = (EmailBusinessRulesData) o;
+
+ return reportId != null ? reportId.equals(that.reportId) : that.reportId == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ return reportId != null ? reportId.hashCode() : 0;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignData.java
new file mode 100644
index 000000000..02ff74e72
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignData.java
@@ -0,0 +1,144 @@
+/**
+ * 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.infrastructure.campaigns.email.data;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class EmailCampaignData {
+
+ @SuppressWarnings("unused")
+ private Long id;
+ @SuppressWarnings("unused")
+ private final String campaignName;
+ @SuppressWarnings("unused")
+ private final Integer campaignType;
+ @SuppressWarnings("unused")
+ private final Long businessRuleId;
+ @SuppressWarnings("unused")
+ private final String paramValue;
+ @SuppressWarnings("unused")
+ private final EnumOptionData campaignStatus;
+ @SuppressWarnings("unused")
+ private final String emailSubject;
+ @SuppressWarnings("unused")
+ private final String emailMessage;
+ @SuppressWarnings("unused")
+ private final String emailAttachmentFileFormat;
+ @SuppressWarnings("unused")
+ private final Long stretchyReportId;
+ @SuppressWarnings("unused")
+ private final String stretchyReportParamMap;
+ @SuppressWarnings("unused")
+ private final DateTime nextTriggerDate;
+ @SuppressWarnings("unused")
+ private final LocalDate lastTriggerDate;
+ @SuppressWarnings("unused")
+ private final EmailCampaignTimeLine emailCampaignTimeLine;
+
+ @SuppressWarnings("unused")
+ private final DateTime recurrenceStartDate;
+
+ private final String recurrence;
+
+ private EmailCampaignData(final Long id, final String campaignName, final Integer campaignType, final Long businessRuleId,
+ final String paramValue, final EnumOptionData campaignStatus, final String emailSubject,
+ final String message, final String emailAttachmentFileFormat, final Long stretchyReportId,
+ final String stretchyReportParamMap, final DateTime nextTriggerDate, final LocalDate lastTriggerDate,
+ final EmailCampaignTimeLine emailCampaignTimeLine, final DateTime recurrenceStartDate, final String recurrence) {
+ this.id = id;
+ this.campaignName = campaignName;
+ this.campaignType = campaignType;
+ this.businessRuleId = businessRuleId;
+ this.paramValue = paramValue;
+ this.campaignStatus =campaignStatus;
+ this.emailSubject = emailSubject;
+ this.emailMessage = message;
+ this.emailAttachmentFileFormat = emailAttachmentFileFormat;
+ this.stretchyReportId = stretchyReportId;
+ this.stretchyReportParamMap = stretchyReportParamMap;
+ if(nextTriggerDate !=null){
+ this.nextTriggerDate = nextTriggerDate;
+ }else{
+ this.nextTriggerDate = null;
+ }
+ if(lastTriggerDate !=null){
+ this.lastTriggerDate = lastTriggerDate;
+ }else{
+ this.lastTriggerDate = null;
+ }
+ this.emailCampaignTimeLine =emailCampaignTimeLine;
+ this.recurrenceStartDate = recurrenceStartDate;
+ this.recurrence = recurrence;
+ }
+
+ public static EmailCampaignData instance(final Long id, final String campaignName, final Integer campaignType, final Long runReportId,
+ final String paramValue, final EnumOptionData campaignStatus, final String emailSubject,
+ final String message, final String emailAttachmentFileFormat, final Long stretchyReportId,
+ final String stretchyReportParamMap, final DateTime nextTriggerDate, final LocalDate lastTriggerDate,
+ final EmailCampaignTimeLine emailCampaignTimeLine,
+ final DateTime recurrenceStartDate, final String recurrence){
+ return new EmailCampaignData(id,campaignName,campaignType,runReportId,paramValue,
+ campaignStatus,emailSubject,message,emailAttachmentFileFormat,stretchyReportId,stretchyReportParamMap,nextTriggerDate,lastTriggerDate,emailCampaignTimeLine,recurrenceStartDate,recurrence);
+ }
+
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getCampaignName() {
+ return this.campaignName;
+ }
+
+ public Integer getCampaignType() {
+ return this.campaignType;
+ }
+
+ public Long getRunReportId() {
+ return this.businessRuleId;
+ }
+
+ public String getParamValue() {
+ return this.paramValue;
+ }
+
+ public EnumOptionData getCampaignStatus() {
+ return this.campaignStatus;
+ }
+
+ public String getEmailSubject() { return this.emailSubject; }
+
+ public String getMessage() {
+ return this.emailMessage;
+ }
+
+ public DateTime getNextTriggerDate() {
+ return this.nextTriggerDate;
+ }
+
+ public LocalDate getLastTriggerDate() {
+ return this.lastTriggerDate;
+ }
+
+ public String getRecurrence() {return this.recurrence;}
+
+ public DateTime getRecurrenceStartDate() {return this.recurrenceStartDate;}
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignTimeLine.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignTimeLine.java
new file mode 100644
index 000000000..920462690
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignTimeLine.java
@@ -0,0 +1,40 @@
+/**
+ * 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.infrastructure.campaigns.email.data;
+
+import org.joda.time.LocalDate;
+
+public class EmailCampaignTimeLine {
+ private final LocalDate submittedOnDate;
+ private final String submittedByUsername;
+ private final LocalDate activatedOnDate;
+ private final String activatedByUsername;
+ private final LocalDate closedOnDate;
+ private final String closedByUsername;
+
+ public EmailCampaignTimeLine(final LocalDate submittedOnDate, final String submittedByUsername,
+ final LocalDate activatedOnDate, final String activatedByUsername, final LocalDate closedOnDate, final String closedByUsername) {
+ this.submittedOnDate = submittedOnDate;
+ this.submittedByUsername = submittedByUsername;
+ this.activatedOnDate = activatedOnDate;
+ this.activatedByUsername = activatedByUsername;
+ this.closedOnDate = closedOnDate;
+ this.closedByUsername = closedByUsername;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignValidator.java
new file mode 100644
index 000000000..d85f38693
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/data/EmailCampaignValidator.java
@@ -0,0 +1,259 @@
+/**
+ * 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.infrastructure.campaigns.email.data;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.lang.StringUtils;
+import org.joda.time.LocalDate;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Type;
+import java.util.*;
+
+@Component
+public class EmailCampaignValidator {
+
+
+
+ public static final String RESOURCE_NAME = "email";
+ public static final String campaignName = "campaignName";
+ public static final String campaignType = "campaignType";
+ public static final String businessRuleId = "businessRuleId";
+ public static final String stretchyReportId = "stretchyReportId";
+ public static final String stretchyReportParamMap = "stretchyReportParamMap";
+ public static final String paramValue = "paramValue";
+ public static final String emailSubject = "emailSubject";
+ public static final String emailMessage = "emailMessage";
+ public static final String emailAttachmentFileFormatId = "emailAttachmentFileFormatId";
+ public static final String activationDateParamName = "activationDate";
+ public static final String recurrenceStartDate = "recurrenceStartDate";
+ public static final String submittedOnDateParamName = "submittedOnDate";
+ public static final String closureDateParamName = "closureDate";
+ public static final String recurrenceParamName = "recurrence";
+ public static final String statusParamName = "status";
+
+ public static final String localeParamName = "locale";
+ public static final String dateFormatParamName = "dateFormat";
+
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+
+ public static final Set supportedParams = new HashSet(Arrays.asList(campaignName, campaignType,localeParamName,dateFormatParamName,
+ businessRuleId,paramValue,emailMessage,recurrenceStartDate,activationDateParamName,submittedOnDateParamName,closureDateParamName,recurrenceParamName,
+ emailSubject,stretchyReportId,stretchyReportParamMap,emailAttachmentFileFormatId));
+
+ public static final Set supportedParamsForUpdate = new HashSet<>(Arrays.asList(campaignName, campaignType,localeParamName,dateFormatParamName,
+ businessRuleId,paramValue,emailMessage,recurrenceStartDate,activationDateParamName,recurrenceParamName));
+
+ public static final Set ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName,
+ dateFormatParamName, activationDateParamName));
+
+ public static final Set CLOSE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName,
+ dateFormatParamName, closureDateParamName));
+
+ public static final Set PREVIEW_REQUEST_DATA_PARAMETERS= new HashSet(Arrays.asList(paramValue,emailMessage));
+
+ @Autowired
+ public EmailCampaignValidator(FromJsonHelper fromApiJsonHelper) {
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ }
+
+
+ public void validateCreate(String json){
+ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+ final Type typeOfMap = new TypeToken