You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ra...@apache.org on 2016/11/14 14:06:09 UTC

[07/14] incubator-fineract git commit: SMS Campaign feature implementation

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
new file mode 100644
index 0000000..c4d47e8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
@@ -0,0 +1,687 @@
+/**
+ * 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.sms.service;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fineract.infrastructure.campaigns.constants.CampaignType;
+import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus;
+import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
+import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignPreviewData;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
+import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToBeDeletedException;
+import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToEditException;
+import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound;
+import org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
+import org.apache.fineract.infrastructure.dataqueries.domain.Report;
+import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository;
+import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
+import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
+import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.group.domain.GroupRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import com.google.gson.JsonElement;
+
+@Service
+public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(SmsCampaignWritePlatformServiceJpaImpl.class);
+
+    private final PlatformSecurityContext context;
+
+    private final SmsCampaignRepository smsCampaignRepository;
+    private final SmsCampaignValidator smsCampaignValidator;
+    private final ReportRepository reportRepository;
+    private final SmsMessageRepository smsMessageRepository;
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+    private final GroupRepository groupRepository;
+    private final ReadReportingService readReportingService;
+    private final GenericDataService genericDataService;
+    private final FromJsonHelper fromJsonHelper;
+
+    private final SmsMessageScheduledJobService smsMessageScheduledJobService;
+    
+    @Autowired
+    public SmsCampaignWritePlatformServiceJpaImpl(final PlatformSecurityContext context, final SmsCampaignRepository smsCampaignRepository,
+            final SmsCampaignValidator smsCampaignValidator, final ReportRepository reportRepository,
+            final SmsMessageRepository smsMessageRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
+            final ReadReportingService readReportingService, final GenericDataService genericDataService,
+            final FromJsonHelper fromJsonHelper, final GroupRepository groupRepository,
+            final SmsMessageScheduledJobService smsMessageScheduledJobService) {
+        this.context = context;
+        this.smsCampaignRepository = smsCampaignRepository;
+        this.smsCampaignValidator = smsCampaignValidator;
+        this.reportRepository = reportRepository;
+        this.smsMessageRepository = smsMessageRepository;
+        this.clientRepositoryWrapper = clientRepositoryWrapper;
+        this.readReportingService = readReportingService;
+        this.genericDataService = genericDataService;
+        this.fromJsonHelper = fromJsonHelper;
+        this.groupRepository = groupRepository;
+        this.smsMessageScheduledJobService = smsMessageScheduledJobService ;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult create(JsonCommand command) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        this.smsCampaignValidator.validateCreate(command.json());
+        final Long runReportId = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId);
+        Report report = this.reportRepository.findOne(runReportId);
+        if (report == null) { throw new ReportNotFoundException(runReportId); }
+        SmsCampaign smsCampaign = SmsCampaign.instance(currentUser, report, command);
+
+        this.smsCampaignRepository.save(smsCampaign);
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(smsCampaign.getId()) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult update(final Long resourceId, final JsonCommand command) {
+        try {
+            this.context.authenticatedUser();
+
+            this.smsCampaignValidator.validateForUpdate(command.json());
+            final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(resourceId);
+
+            if (smsCampaign == null) { throw new SmsCampaignNotFound(resourceId); }
+            if (smsCampaign.isActive()) { throw new SmsCampaignMustBeClosedToEditException(smsCampaign.getId()); }
+            final Map<String, Object> changes = smsCampaign.update(command);
+
+            if (changes.containsKey(SmsCampaignValidator.runReportId)) {
+                final Long newValue = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId);
+                final Report reportId = this.reportRepository.findOne(newValue);
+                if (reportId == null) { throw new ReportNotFoundException(newValue); }
+                ;
+                smsCampaign.updateBusinessRuleId(reportId);
+            }
+
+            if (!changes.isEmpty()) {
+                this.smsCampaignRepository.saveAndFlush(smsCampaign);
+            }
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(resourceId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return CommandProcessingResult.empty();
+        }
+
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult delete(final Long resourceId) {
+        this.context.authenticatedUser();
+        final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(resourceId);
+
+        if (smsCampaign == null) { throw new SmsCampaignNotFound(resourceId); }
+        if (smsCampaign.isActive()) { throw new SmsCampaignMustBeClosedToBeDeletedException(smsCampaign.getId()); }
+
+        /*
+         * Do not delete but set a boolean is_visible to zero
+         */
+        smsCampaign.delete();
+        this.smsCampaignRepository.saveAndFlush(smsCampaign);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(smsCampaign.getId()) //
+                .build();
+
+    }
+
+    private void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign) {
+        try {
+            HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+
+            HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+
+            List<HashMap<String, Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),
+                    queryParamForRunReport);
+
+            if (runReportObject != null) {
+                for (HashMap<String, Object> entry : runReportObject) {
+                    String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), entry);
+                    Integer clientId = (Integer) entry.get("id");
+                    Object mobileNo = entry.get("mobileNo");
+
+                    Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
+                    if (mobileNo != null) {
+//                        String countryCode = this.smsReadPlatformService.retrieveCountryCode(client.getOffice().getId()).getCountryCode();
+                        SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNo.toString(),
+                                smsCampaign);
+                        this.smsMessageRepository.save(smsMessage);
+                    }
+                }
+            }
+        } catch (final IOException e) {
+            // TODO throw something here
+            System.out.println(e.getMessage());
+        }
+
+    }
+
+    @Override
+    public void insertDirectCampaignIntoSmsOutboundTable(final Loan loan, final SmsCampaign smsCampaign) {
+        try {
+            if (loan.hasInvalidLoanType()) { throw new InvalidLoanTypeException("Loan Type cannot be 0 for the Triggered Sms Campaign"); }
+
+            Set<Client> clientSet = new HashSet<>();
+
+            HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+            campaignParams.put("loanId", loan.getId().toString());
+            
+            HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+            queryParamForRunReport.put("loanId", loan.getId().toString());
+
+            if (loan.isGroupLoan()) {
+                Group group = this.groupRepository.findOne(loan.getGroupId());
+                clientSet.addAll(group.getClientMembers());
+                queryParamForRunReport.put("groupId", group.getId().toString());
+            } else {
+                Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(loan.getClientId());
+                clientSet.add(client);
+            }
+
+            for (Client client : clientSet) {
+                campaignParams.put("clientId", client.getId().toString());
+                queryParamForRunReport.put("clientId", client.getId().toString());
+                
+                List<HashMap<String, Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),
+                        queryParamForRunReport);
+
+                if (runReportObject != null && runReportObject.size() > 0) {
+                    for (HashMap<String, Object> entry : runReportObject) {
+                        String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), entry);
+                        Object mobileNo = entry.get("mobileNo");
+                        
+                        if (mobileNo != null) {
+                            SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNo.toString(),
+                                    smsCampaign);
+                            smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
+                            this.smsMessageRepository.save(smsMessage);
+                            Collection<SmsMessage> messages = new ArrayList<>() ;
+                            messages.add(smsMessage) ;
+                            Map<SmsCampaign, Collection<SmsMessage>> smsDataMap = new HashMap<>() ;
+                            smsDataMap.put(smsCampaign, messages) ;
+                             this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap);
+                        }
+                    }
+                }
+            }
+        } catch (final IOException e) {
+            System.out.println("IOException: " + e.getMessage());
+        } catch (final RuntimeException e) {
+            System.out.println("RuntimeException: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void insertDirectCampaignIntoSmsOutboundTable(final Client client, final SmsCampaign smsCampaign) {
+        try {
+            HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+            campaignParams.put("clientId", client.getId().toString());
+            HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+            
+			campaignParams.put("clientId", client.getId().toString());
+			queryParamForRunReport.put("clientId", client.getId().toString());
+			
+			List<HashMap<String, Object>> runReportObject = this
+					.getRunReportByServiceImpl(campaignParams.get("reportName"), queryParamForRunReport);
+
+			if (runReportObject != null && runReportObject.size() > 0) {
+				for (HashMap<String, Object> entry : runReportObject) {
+					String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(),
+							smsCampaign.getCampaignName(), entry);
+					Object mobileNo = entry.get("mobileNo");
+
+					if (mobileNo != null) {
+						SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage,
+								mobileNo.toString(), smsCampaign);
+						smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
+						this.smsMessageRepository.save(smsMessage);
+						Collection<SmsMessage> messages = new ArrayList<>();
+						messages.add(smsMessage);
+						Map<SmsCampaign, Collection<SmsMessage>> smsDataMap = new HashMap<>();
+						smsDataMap.put(smsCampaign, messages);
+						this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap);
+					}
+				}
+			}
+        } catch (final IOException e) {
+            System.out.println("IOException: " + e.getMessage());
+        } catch (final RuntimeException e) {
+            System.out.println("RuntimeException: " + e.getMessage());
+        }
+    }
+    
+    @Override
+    public void insertDirectCampaignIntoSmsOutboundTable(final SavingsAccount savingsAccount, final SmsCampaign smsCampaign) {
+        try {
+            HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+            campaignParams.put("savingsId", savingsAccount.getId().toString());
+            HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
+                    new TypeReference<HashMap<String, String>>() {});
+            queryParamForRunReport.put("savingsId", savingsAccount.getId().toString());
+            
+            Client client = savingsAccount.getClient() ;
+			List<HashMap<String, Object>> runReportObject = this
+					.getRunReportByServiceImpl(campaignParams.get("reportName"), queryParamForRunReport);
+
+			if (runReportObject != null && runReportObject.size() > 0) {
+				for (HashMap<String, Object> entry : runReportObject) {
+					String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(),
+							smsCampaign.getCampaignName(), entry);
+					Object mobileNo = entry.get("mobileNo");
+
+					if (mobileNo != null) {
+						SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage,
+								mobileNo.toString(), smsCampaign);
+						smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
+						this.smsMessageRepository.save(smsMessage);
+						Collection<SmsMessage> messages = new ArrayList<>();
+						messages.add(smsMessage);
+						Map<SmsCampaign, Collection<SmsMessage>> smsDataMap = new HashMap<>();
+						smsDataMap.put(smsCampaign, messages);
+						this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap);
+					}
+				}
+			}
+        } catch (final IOException e) {
+            System.out.println("IOException: " + e.getMessage());
+        } catch (final RuntimeException e) {
+            System.out.println("RuntimeException: " + e.getMessage());
+        }
+    }
+    
+    private void updateTriggerDates(Long campaignId) {
+        final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId);
+        if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); }
+        LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate();
+        smsCampaign.setLastTriggerDate(nextTriggerDate.toDate());
+        // calculate new trigger date and insert into next trigger date
+
+        /**
+         * next run time has to be in the future if not calculate a new future
+         * date
+         */
+        LocalDate nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate()
+                .toLocalDate(), nextTriggerDate.toLocalDate());
+        if (nextRuntime.isBefore(DateUtils.getLocalDateOfTenant())) { // means
+                                                                      // next
+                                                                      // run
+                                                                      // time is
+                                                                      // in the
+                                                                      // past
+                                                                      // calculate
+                                                                      // a new
+                                                                      // future
+                                                                      // date
+            nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate().toLocalDate(),
+                    DateUtils.getLocalDateOfTenant());
+        }
+        final LocalDateTime getTime = smsCampaign.getRecurrenceStartDateTime();
+        final String dateString = nextRuntime.toString() + " " + getTime.getHourOfDay() + ":" + getTime.getMinuteOfHour() + ":"
+                + getTime.getSecondOfMinute();
+        final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+        final LocalDateTime newTriggerDateWithTime = LocalDateTime.parse(dateString, simpleDateFormat);
+
+        smsCampaign.setNextTriggerDate(newTriggerDateWithTime.toDate());
+        this.smsCampaignRepository.saveAndFlush(smsCampaign);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult activateSmsCampaign(Long campaignId, JsonCommand command) {
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        this.smsCampaignValidator.validateActivation(command.json());
+
+        final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId);
+
+        if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate");
+
+        smsCampaign.activate(currentUser, fmt, activationDate);
+
+        this.smsCampaignRepository.saveAndFlush(smsCampaign);
+
+        if (smsCampaign.isDirect()) {
+            insertDirectCampaignIntoSmsOutboundTable(smsCampaign);
+        } else if (smsCampaign.isSchedule()) {
+
+            /**
+             * if recurrence start date is in the future calculate next trigger
+             * date if not use recurrence start date us next trigger date when
+             * activating
+             */
+            LocalDate nextTriggerDate = null;
+            if (smsCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())) {
+                nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getRecurrenceStartDate(),
+                        DateUtils.getLocalDateOfTenant());
+            } else {
+                nextTriggerDate = smsCampaign.getRecurrenceStartDate();
+            }
+            // to get time of tenant
+            final LocalDateTime getTime = smsCampaign.getRecurrenceStartDateTime();
+
+            final String dateString = nextTriggerDate.toString() + " " + getTime.getHourOfDay() + ":" + getTime.getMinuteOfHour() + ":"
+                    + getTime.getSecondOfMinute();
+            final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+            final LocalDateTime nextTriggerDateWithTime = LocalDateTime.parse(dateString, simpleDateFormat);
+
+            smsCampaign.setNextTriggerDate(nextTriggerDateWithTime.toDate());
+            this.smsCampaignRepository.saveAndFlush(smsCampaign);
+        }
+
+        /*
+         * if campaign is direct insert campaign message into sms outbound table
+         * else if its a schedule create a job process for it
+         */
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(smsCampaign.getId()) //
+                .build();
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult closeSmsCampaign(Long campaignId, JsonCommand command) {
+
+        final AppUser currentUser = this.context.authenticatedUser();
+        this.smsCampaignValidator.validateClosedDate(command.json());
+
+        final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId);
+        if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate closureDate = command.localDateValueOfParameterNamed("closureDate");
+
+        smsCampaign.close(currentUser, fmt, closureDate);
+
+        this.smsCampaignRepository.saveAndFlush(smsCampaign);
+//        this.serviceui.sendMessagesToGateway();
+
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(smsCampaign.getId()) //
+                .build();
+    }
+
+    @Override
+    public String compileSmsTemplate(final String textMessageTemplate, final String campaignName, final Map<String, Object> smsParams) {
+        final MustacheFactory mf = new DefaultMustacheFactory();
+        final Mustache mustache = mf.compile(new StringReader(textMessageTemplate), campaignName);
+
+        final StringWriter stringWriter = new StringWriter();
+        mustache.execute(stringWriter, smsParams);
+
+        return stringWriter.toString();
+    }
+
+    private List<HashMap<String, Object>> getRunReportByServiceImpl(final String reportName, final Map<String, String> queryParams)
+            throws IOException {
+        final String reportType = "report";
+
+        List<HashMap<String, Object>> resultList = new ArrayList<>();
+        final GenericResultsetData results = this.readReportingService.retrieveGenericResultSetForSmsCampaign(reportName, reportType,
+                queryParams);
+
+        try {
+            final String response = this.genericDataService.generateJsonFromGenericResultsetData(results);
+            resultList = new ObjectMapper().readValue(response, new TypeReference<List<HashMap<String, Object>>>() {});
+        } catch (JsonParseException e) {
+            logger.info("Conversion of report query results to JSON failed: " + e.getMessage() + " - Location: " + e.getLocation());
+            return resultList;
+        }
+        // loop changes array date to string date
+        for (HashMap<String, Object> entry : resultList) {
+            for (Map.Entry<String, Object> map : entry.entrySet()) {
+                String key = map.getKey();
+                Object ob = map.getValue();
+                if (ob instanceof ArrayList && ((ArrayList) ob).size() == 3) {
+                    String changeArrayDateToStringDate = ((ArrayList) ob).get(2).toString() + "-" + ((ArrayList) ob).get(1).toString()
+                            + "-" + ((ArrayList) ob).get(0).toString();
+                    entry.put(key, changeArrayDateToStringDate);
+                }
+            }
+        }
+        return resultList;
+    }
+
+    @Override
+    public CampaignPreviewData previewMessage(final JsonQuery query) {
+        CampaignPreviewData campaignMessage = null;
+        this.context.authenticatedUser();
+        this.smsCampaignValidator.validatePreviewMessage(query.json());
+        // final String smsParams =
+        // this.fromJsonHelper.extractJsonObjectNamed("paramValue",
+        // query.parsedJson()).getAsString();
+        final JsonElement smsParamsElement = this.fromJsonHelper
+                .extractJsonObjectNamed(SmsCampaignValidator.paramValue, query.parsedJson());
+        String smsParams = smsParamsElement.toString();
+        final String textMessageTemplate = this.fromJsonHelper.extractStringNamed("message", query.parsedJson());
+
+        try {
+            HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsParams,
+                    new TypeReference<HashMap<String, String>>() {});
+
+            HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsParams,
+                    new TypeReference<HashMap<String, String>>() {});
+
+            List<HashMap<String, Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),
+                    queryParamForRunReport);
+
+            if (runReportObject != null && !runReportObject.isEmpty()) {
+                for (HashMap<String, Object> entry : runReportObject) {
+                    // add string object to campaignParam object
+                    String textMessage = this.compileSmsTemplate(textMessageTemplate, "SmsCampaign", entry);
+                    if (!textMessage.isEmpty()) {
+                        final Integer totalMessage = runReportObject.size();
+                        campaignMessage = new CampaignPreviewData(textMessage, totalMessage);
+                        break;
+                    }
+                }
+            } else {
+                campaignMessage = new CampaignPreviewData(textMessageTemplate, 0);
+            }
+        } catch (final IOException e) {
+            // TODO throw something here
+        }
+        return campaignMessage;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult reactivateSmsCampaign(final Long campaignId, JsonCommand command) {
+
+        this.smsCampaignValidator.validateActivation(command.json());
+
+        final AppUser currentUser = this.context.authenticatedUser();
+
+        final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId);
+
+        if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); }
+
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
+        final LocalDate reactivationDate = command.localDateValueOfParameterNamed("activationDate");
+        smsCampaign.reactivate(currentUser, fmt, reactivationDate);
+        if (smsCampaign.isDirect()) {
+            insertDirectCampaignIntoSmsOutboundTable(smsCampaign);
+        } else if (smsCampaign.isSchedule()) {
+
+            /**
+             * if recurrence start date is in the future calculate next trigger
+             * date if not use recurrence start date us next trigger date when
+             * activating
+             */
+            LocalDate nextTriggerDate = null;
+            if (smsCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())) {
+                nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getRecurrenceStartDate(),
+                        DateUtils.getLocalDateOfTenant());
+            } else {
+                nextTriggerDate = smsCampaign.getRecurrenceStartDate();
+            }
+            // to get time of tenant
+            final LocalDateTime getTime = smsCampaign.getRecurrenceStartDateTime();
+
+            final String dateString = nextTriggerDate.toString() + " " + getTime.getHourOfDay() + ":" + getTime.getMinuteOfHour() + ":"
+                    + getTime.getSecondOfMinute();
+            final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+            final LocalDateTime nextTriggerDateWithTime = LocalDateTime.parse(dateString, simpleDateFormat);
+
+            smsCampaign.setNextTriggerDate(nextTriggerDateWithTime.toDate());
+        }
+        this.smsCampaignRepository.saveAndFlush(smsCampaign);
+
+        return new CommandProcessingResultBuilder() //
+                .withEntityId(smsCampaign.getId()) //
+                .build();
+
+    }
+
+    @Override
+    @CronTarget(jobName = JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE)
+    public void storeTemplateMessageIntoSmsOutBoundTable() throws JobExecutionException {
+        final Collection<SmsCampaign> smsCampaignDataCollection = this.smsCampaignRepository.findByCampaignTypeAndTriggerTypeAndStatus(
+                CampaignType.SMS.getValue(), SmsCampaignTriggerType.SCHEDULE.getValue(), SmsCampaignStatus.ACTIVE.getValue());
+        if (smsCampaignDataCollection != null) {
+            for (SmsCampaign smsCampaign : smsCampaignDataCollection) {
+                LocalDateTime tenantDateNow = tenantDateTime();
+                LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate();
+
+                logger.info("tenant time " + tenantDateNow.toString() + " trigger time " + nextTriggerDate.toString() + JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name());
+                if (nextTriggerDate.isBefore(tenantDateNow)) {
+                    insertDirectCampaignIntoSmsOutboundTable(smsCampaign);
+                    this.updateTriggerDates(smsCampaign.getId());
+                }
+            }
+        }
+    }
+
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+        final Throwable realCause = dve.getMostSpecificCause();
+
+        throw new PlatformDataIntegrityException("error.msg.sms.campaign.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource: " + realCause.getMessage());
+    }
+
+    private LocalDateTime tenantDateTime() {
+        LocalDateTime today = new LocalDateTime();
+        final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+
+        if (tenant != null) {
+            final DateTimeZone zone = DateTimeZone.forID(tenant.getTimezoneId());
+            if (zone != null) {
+                today = new LocalDateTime(zone);
+            }
+        }
+        return today;
+    }
+    
+//    private String formatDestinationPhoneNumber(String phoneNumber, String countryCallingCode) {
+//        StringBuilder formatedPhoneNumber = new StringBuilder("+");
+//
+//        try {
+//            Long phoneNumberToLong = Long.parseLong(phoneNumber);
+//            Long countryCallingCodeToLong = Long.parseLong(countryCallingCode);
+//            formatedPhoneNumber.append(Long.toString(countryCallingCodeToLong));
+//            formatedPhoneNumber.append(Long.toString(phoneNumberToLong));
+//        }
+//
+//        catch (Exception e) {
+//            logger.error("Invalid phone number or country calling code, must contain only numbers", e);
+//        }
+//
+//        return formatedPhoneNumber.toString();
+//    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java
index 698ee43..9b5aeb2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import org.apache.commons.lang.StringUtils;
 import org.apache.fineract.infrastructure.configuration.exception.ExternalServiceConfigurationNotFoundException;
 import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.S3_JSON_INPUT_PARAMS;
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.SMS_JSON_INPUT_PARAMS;
 import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.SMTP_JSON_INPUT_PARAMS;
 import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
@@ -38,6 +39,7 @@ public class ExternalServicesPropertiesCommandFromApiJsonDeserializer {
 
     private final Set<String> S3SupportedParameters = S3_JSON_INPUT_PARAMS.getAllValues();
     private final Set<String> SMTPSupportedParameters = SMTP_JSON_INPUT_PARAMS.getAllValues();
+    private final Set<String> SMSSupportedParameters = SMS_JSON_INPUT_PARAMS.getAllValues();
     private final FromJsonHelper fromApiJsonHelper;
 
     @Autowired
@@ -58,6 +60,10 @@ public class ExternalServicesPropertiesCommandFromApiJsonDeserializer {
                 this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.SMTPSupportedParameters);
             break;
 
+            case "SMS":
+                this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.SMSSupportedParameters);
+            break;
+
             default:
                 throw new ExternalServiceConfigurationNotFoundException(externalServiceName);
         }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java
index c36b444..44a56ca 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java
@@ -35,6 +35,12 @@ public class ExternalServicesConstants {
     public static final String SMTP_PORT = "port";
     public static final String SMTP_USE_TLS = "useTLS";
 
+    public static final String SMS_SERVICE_NAME = "MESSAGE_GATEWAY";
+    public static final String SMS_HOST = "host_name";
+    public static final String SMS_PORT = "port_number";
+    public static final String SMS_END_POINT = "end_point";
+    public static final String SMS_TENANT_APP_KEY = "tenant_app_key";
+    
     public static enum EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS {
         EXTERNAL_SERVICE_ID("external_service_id"), NAME("name"), VALUE("value");
 
@@ -96,6 +102,37 @@ public class ExternalServicesConstants {
             return this.value;
         }
     }
+    
+    public static enum SMS_JSON_INPUT_PARAMS {
+        HASTNAME("host_name"), PORT("port_number"), END_POINT("end_point"), TENANT_APP_KEY("tenant_app_key");
+
+        private final String value;
+
+        private SMS_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+
+        static {
+            for (final SMS_JSON_INPUT_PARAMS type : SMS_JSON_INPUT_PARAMS.values()) {
+                values.add(type.value);
+            }
+        }
+
+        public static Set<String> getAllValues() {
+            return values;
+        }
+
+        @Override
+        public String toString() {
+            return name().toString().replaceAll("_", " ");
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
 
     public static enum S3_JSON_INPUT_PARAMS {
         S3_ACCESS_KEY("s3_access_key"), S3_BUCKET_NAME("s3_bucket_name"), S3_SECRET_KEY("s3_secret_key");

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java
index 023d06c..2d46835 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.configuration.service;
 
 import java.util.Collection;
 
+import org.apache.fineract.infrastructure.campaigns.sms.data.MessageGatewayConfigurationData;
 import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData;
 import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData;
 import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData;
@@ -30,6 +31,8 @@ public interface ExternalServicesPropertiesReadPlatformService {
 
     SMTPCredentialsData getSMTPCredentials();
 
+    MessageGatewayConfigurationData getSMSGateway();
+
     Collection<ExternalServicesPropertiesData> retrieveOne(String serviceName);
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
index a01d2a5..ccd4012 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java
@@ -22,6 +22,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Collection;
 
+import org.apache.fineract.infrastructure.campaigns.sms.data.MessageGatewayConfigurationData;
 import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData;
 import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData;
 import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData;
@@ -107,6 +108,30 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
 
     }
 
+    private static final class MessageGatewayDataExtractor implements ResultSetExtractor<MessageGatewayConfigurationData> {
+
+        @Override
+        public MessageGatewayConfigurationData extractData(final ResultSet rs) throws SQLException, DataAccessException {
+            String host = null;
+            int port = 9191;
+            String endPoint = null;
+            String tenantAppKey = null;
+
+            while (rs.next()) {
+                if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_HOST)) {
+                    host = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_PORT)) {
+                    port = rs.getInt("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_END_POINT)) {
+                    endPoint = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_TENANT_APP_KEY)) {
+                    tenantAppKey = rs.getString("value");
+                }
+            }
+            return new MessageGatewayConfigurationData(null, null, host, port, endPoint, null, null, false, tenantAppKey);
+        }
+    }
+
     @Override
     public S3CredentialsData getS3Credentials() {
         final ResultSetExtractor<S3CredentialsData> resultSetExtractor = new S3CredentialsDataExtractor();
@@ -127,6 +152,15 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
     }
 
     @Override
+    public MessageGatewayConfigurationData getSMSGateway() {
+        final ResultSetExtractor<MessageGatewayConfigurationData> resultSetExtractor = new MessageGatewayDataExtractor();
+        final String sql = "SELECT esp.name, esp.value FROM c_external_service_properties esp inner join c_external_service es on esp.external_service_id = es.id where es.name = '"
+                + ExternalServicesConstants.SMS_SERVICE_NAME + "'";
+        final MessageGatewayConfigurationData messageGatewayConfigurationData = this.jdbcTemplate.query(sql, resultSetExtractor, new Object[] {});
+        return messageGatewayConfigurationData;
+    }
+
+    @Override
     public Collection<ExternalServicesPropertiesData> retrieveOne(String serviceName) {
         String serviceNameToUse = null;
         switch (serviceName) {
@@ -138,6 +172,10 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
                 serviceNameToUse = ExternalServicesConstants.SMTP_SERVICE_NAME;
             break;
 
+            case "SMS":
+                serviceNameToUse = ExternalServicesConstants.SMS_SERVICE_NAME;
+            break;
+
             default:
                 throw new ExternalServiceConfigurationNotFoundException(serviceName);
         }
@@ -147,4 +185,5 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
         return this.jdbcTemplate.query(sql, mapper, new Object[] {});
 
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java
index db25cea..44f4164 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java
@@ -54,6 +54,10 @@ public class ExternalServicesReadPlatformServiceImpl implements ExternalServices
                 serviceNameToUse = ExternalServicesConstants.SMTP_SERVICE_NAME;
             break;
 
+            case "SMS":
+                serviceNameToUse = ExternalServicesConstants.SMS_SERVICE_NAME;
+            break;
+
             default:
                 throw new ExternalServiceConfigurationNotFoundException(serviceName);
         }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
index 93c9a5e..9d14ea9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
@@ -270,6 +270,10 @@ public class FromJsonHelper {
         return this.helperDelegator.extractMonthDayFormatParameter(element);
     }
 
+    public JsonObject extractJsonObjectNamed(final String parameterName, final JsonElement element) {
+        return this.helperDelegator.extractJsonObjectNamed(parameterName, element);
+    }
+
     public Gson getGsonConverter() {
         return this.gsonConverter;
     }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
index 2ce0b00..a3e489a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
@@ -271,6 +271,19 @@ public class JsonParserHelper {
         return jsonArray;
     }
 
+    public JsonObject extractJsonObjectNamed(final String parameterName, final JsonElement element) {
+        JsonObject jsonObject = null;
+
+        if (element.isJsonObject()) {
+            final JsonObject object = element.getAsJsonObject();
+            if (object.has(parameterName)) {
+                jsonObject = object.get(parameterName).getAsJsonObject();
+            }
+        }
+
+        return jsonObject;
+    }
+
     /**
      * Used with the local date is in array format
      */

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java
index 827bacd..30a78f3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java
@@ -36,4 +36,14 @@ public class PaginationHelper<E> {
 
         return new Page<>(items, totalFilteredRecords);
     }
+
+    public Page<Long> fetchPage(JdbcTemplate jdbcTemplate, String sql, String sqlCountRows, Class<Long> type) {
+        final List<Long> items = jdbcTemplate.queryForList(sql, type);
+
+        // determine how many rows are available
+        @SuppressWarnings("deprecation")
+        final int totalFilteredRecords = jdbcTemplate.queryForInt(sqlCountRows);
+
+        return new Page<>(items, totalFilteredRecords);
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
index c1da252..34d397f 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
@@ -209,6 +209,22 @@ public final class SearchParameters {
                 staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
     }
 
+    public static SearchParameters forSMSCampaign(final String sqlSearch, final Integer offset, final Integer limit, final String orderBy,
+            final String sortOrder) {
+
+        final String externalId = null;
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final String accountNo = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
+                staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+
     private SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name,
             final String hierarchy, final String firstname, final String lastname, final Integer offset, final Integer limit,
             final String orderBy, final String sortOrder, final Long staffId, final String accountNo, final Long loanId,

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
index eb75fe0..a21f645 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
@@ -44,4 +44,9 @@ public interface ReadReportingService {
     ReportData retrieveReport(final Long id);
 
     Collection<String> getAllowedReportTypes();
+    
+  //needed for smsCampaign jobs where securityContext is null
+    GenericResultsetData retrieveGenericResultSetForSmsCampaign(String name, String type, Map<String, String> extractedQueryParams);
+    
+    String  sqlToRunForSmsCampaign(String name, String type, Map<String, String> queryParams);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
index a6e97ae..f25c371 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
@@ -484,4 +484,35 @@ public class ReadReportingServiceImpl implements ReadReportingService {
             return new ReportParameterData(id, null, null, parameterName);
         }
     }
+
+    @Override
+    public GenericResultsetData retrieveGenericResultSetForSmsCampaign(String name, String type, Map<String, String> queryParams) {
+        final long startTime = System.currentTimeMillis();
+        logger.info("STARTING REPORT: " + name + "   Type: " + type);
+
+        final String sql = sqlToRunForSmsCampaign(name, type, queryParams);
+
+        final GenericResultsetData result = this.genericDataService.fillGenericResultSet(sql);
+
+        final long elapsed = System.currentTimeMillis() - startTime;
+        logger.info("FINISHING Report/Request Name: " + name + " - " + type + "     Elapsed Time: " + elapsed);
+        return result;
+    }
+    
+    @Override
+    public String sqlToRunForSmsCampaign(final String name, final String type, final Map<String, String> queryParams) {
+        String sql = getSql(name, type);
+
+        final Set<String> keys = queryParams.keySet();
+        for (String key : keys) {
+            final String pValue = queryParams.get(key);
+            // logger.info("(" + key + " : " + pValue + ")");
+            key = "${" + key + "}";
+            sql = this.genericDataService.replace(sql, key, pValue);
+        }
+
+        sql = this.genericDataService.wrapSQL(sql);
+
+        return sql;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
index d893c3b..e2589e9 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -42,7 +42,10 @@ public enum JobName {
     POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), //
     UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), //
     ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Loans With Income Posted As Transactions"),
-    EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs");
+    EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"),
+    UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update Sms Outbound with campaign message"),
+    SEND_MESSAGES_TO_SMS_GATEWAY("Send messages to SMS gateway"), 
+    GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get delivery reports from SMS gateway");
 
     private final String name;
 

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java
index 2980764..310aea3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java
@@ -36,13 +36,14 @@ public class SmsApiConstants {
     public static final String clientIdParamName = "clientId";
     public static final String staffIdParamName = "staffId";
     public static final String messageParamName = "message";
+    public static final String campaignIdParamName = "campaignId";
 
     // response parameters
     public static final String statusParamName = "status";
 
     public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
-            dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName));
+            dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName, campaignIdParamName));
 
-    public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName));
+    public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName, campaignIdParamName));
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
index 506dde8..9f73ea8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.infrastructure.sms.api;
 
 import java.util.Collection;
+import java.util.Date;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -28,10 +29,12 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriInfo;
 
+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;
@@ -39,6 +42,8 @@ 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.sms.data.SmsData;
 import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
@@ -103,6 +108,35 @@ public class SmsApiResource {
         return this.toApiJsonSerializer.serialize(settings, smsMessage);
     }
 
+    @GET
+    @Path("{campaignId}/messageByStatus")
+    public String retrieveAllSmsByStatus(@PathParam("campaignId") final Long campaignId, @Context final UriInfo uriInfo,
+            @QueryParam("status") final Long status, @QueryParam("fromDate") final DateParam fromDateParam,
+            @QueryParam("toDate") final DateParam toDateParam, @QueryParam("locale") final String locale,
+            @QueryParam("dateFormat") final String dateFormat, @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) {
+
+        this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+        final SearchParameters searchParameters = SearchParameters.forSMSCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
+
+        Date fromDate = null;
+        if (fromDateParam != null) {
+            fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
+        }
+        Date toDate = null;
+        if (toDateParam != null) {
+            toDate = toDateParam.getDate("toDate", dateFormat, locale);
+        }
+
+        final Page<SmsData> smsMessages = this.readPlatformService.retrieveSmsByStatus(campaignId, searchParameters, status.intValue(), fromDate,
+                toDate);
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, smsMessages);
+    }
+
     @PUT
     @Path("{resourceId}")
     public String update(@PathParam("resourceId") final Long resourceId, final String apiRequestBodyAsJson) {

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java
index d5449e2..1edda9e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java
@@ -25,28 +25,23 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData;
  */
 public class SmsData {
 
-    @SuppressWarnings("unused")
     private final Long id;
-    @SuppressWarnings("unused")
     private final Long groupId;
-    @SuppressWarnings("unused")
     private final Long clientId;
-    @SuppressWarnings("unused")
     private final Long staffId;
-    @SuppressWarnings("unused")
     private final EnumOptionData status;
-    @SuppressWarnings("unused")
     private final String mobileNo;
-    @SuppressWarnings("unused")
     private final String message;
+    private final Long providerId;
+    private final String campaignName;
 
     public static SmsData instance(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status,
-            final String mobileNo, final String message) {
-        return new SmsData(id, groupId, clientId, staffId, status, mobileNo, message);
+            final String mobileNo, final String message, final Long providerId, final String camapignName) {
+        return new SmsData(id, groupId, clientId, staffId, status, mobileNo, message, providerId, camapignName);
     }
 
     private SmsData(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status,
-            final String mobileNo, final String message) {
+            final String mobileNo, final String message, final Long providerId, final String camapignName) {
         this.id = id;
         this.groupId = groupId;
         this.clientId = clientId;
@@ -54,5 +49,44 @@ public class SmsData {
         this.status = status;
         this.mobileNo = mobileNo;
         this.message = message;
+        this.providerId = providerId;
+        this.campaignName = camapignName;
     }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public Long getGroupId() {
+        return this.groupId;
+    }
+
+    public Long getClientId() {
+        return this.clientId;
+    }
+
+    public Long getStaffId() {
+        return this.staffId;
+    }
+
+    public EnumOptionData getStatus() {
+        return this.status;
+    }
+
+    public String getMobileNo() {
+        return this.mobileNo;
+    }
+
+    public String getMessage() {
+        return this.message;
+    }
+
+    public Long getProviderId() {
+        return this.providerId;
+    }
+
+    public String getCampaignName() {
+        return this.campaignName;
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java
new file mode 100644
index 0000000..f932b05
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java
@@ -0,0 +1,125 @@
+/**
+ * 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.sms.data;
+
+import java.util.Collection;
+
+import com.google.gson.Gson;
+
+/**
+ * Immutable data object representing the API request body sent in the POST
+ * request to the "/queue" resource
+ **/
+public class SmsMessageApiQueueResourceData {
+
+    private Long internalId;
+    private String tenantId;
+    private String createdOnDate;
+    private String sourceAddress;
+    private String mobileNumber;
+    private String message;
+    private Long providerId;
+
+    /**
+     * SmsMessageApiQueueResourceData constructor
+     **/
+    private SmsMessageApiQueueResourceData(Long internalId, String mifosTenantIdentifier, String createdOnDate, String sourceAddress,
+            String mobileNumber, String message, Long providerId) {
+        this.internalId = internalId;
+        this.tenantId = mifosTenantIdentifier;
+        this.createdOnDate = createdOnDate;
+        this.sourceAddress = sourceAddress;
+        this.mobileNumber = mobileNumber;
+        this.message = message;
+        this.providerId = providerId;
+    }
+
+    /**
+     * SmsMessageApiQueueResourceData constructor
+     **/
+    protected SmsMessageApiQueueResourceData() {}
+
+    /**
+     * @return a new instance of the SmsMessageApiQueueResourceData class
+     **/
+    public static final SmsMessageApiQueueResourceData instance(Long internalId, String mifosTenantIdentifier, String createdOnDate,
+            String sourceAddress, String mobileNumber, String message, Long providerId) {
+
+        return new SmsMessageApiQueueResourceData(internalId, mifosTenantIdentifier, createdOnDate, sourceAddress, mobileNumber, message,
+                providerId);
+    }
+
+    /**
+     * @return the internalId
+     */
+    public Long getInternalId() {
+        return internalId;
+    }
+
+    /**
+     * @return the mifosTenantIdentifier
+     */
+    public String getTenantId() {
+        return tenantId;
+    }
+
+    /**
+     * @return the createdOnDate
+     */
+    public String getCreatedOnDate() {
+        return createdOnDate;
+    }
+
+    /**
+     * @return the sourceAddress
+     */
+    public String getSourceAddress() {
+        return sourceAddress;
+    }
+
+    /**
+     * @return the mobileNumber
+     */
+    public String getMobileNumber() {
+        return mobileNumber;
+    }
+
+    /**
+     * @return the message
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * @return the providerId
+     */
+    public Long getproviderId() {
+        return providerId;
+    }
+
+    /**
+     * @return JSON representation of the object
+     **/
+    public static String toJsonString(Collection<SmsMessageApiQueueResourceData> smsResourceData) {
+        Gson gson = new Gson();
+
+        return gson.toJson(smsResourceData);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java
new file mode 100644
index 0000000..3858aaf
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java
@@ -0,0 +1,75 @@
+/**
+ * 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.sms.data;
+
+import java.util.List;
+
+import com.google.gson.Gson;
+
+/** 
+ * Immutable data object representing the API request body sent in the POST request to
+ * the "/report" resource 
+ **/
+public class SmsMessageApiReportResourceData {
+	private List<Long> externalIds;
+	private String mifosTenantIdentifier;
+	
+	/** 
+	 * SmsMessageApiReportResourceData constructor 
+	 **/
+	private SmsMessageApiReportResourceData(List<Long> externalIds, String mifosTenantIdentifier) {
+		this.externalIds = externalIds;
+		this.mifosTenantIdentifier = mifosTenantIdentifier;
+	}
+	
+	/** 
+	 * SmsMessageApiReportResourceData constructor 
+	 **/
+	protected SmsMessageApiReportResourceData() {}
+	
+	/** 
+	 * @return new instance of the SmsMessageApiReportResourceData class
+	 **/
+	public static final SmsMessageApiReportResourceData instance(List<Long> externalIds, String mifosTenantIdentifier) {
+		return new SmsMessageApiReportResourceData(externalIds, mifosTenantIdentifier);
+	}
+
+	/**
+	 * @return the externalIds
+	 */
+	public List<Long> getExternalIds() {
+		return externalIds;
+	}
+
+	/**
+	 * @return the mifosTenantIdentifier
+	 */
+	public String getMifosTenantIdentifier() {
+		return mifosTenantIdentifier;
+	}
+	
+	/** 
+	 * @return JSON representation of the object 
+	 **/
+	public String toJsonString() {
+		Gson gson = new Gson();
+		
+		return gson.toJson(this);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java
new file mode 100644
index 0000000..d247bc7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.sms.data;
+
+import java.util.List;
+
+/** 
+ * Immutable data object representing an outbound SMS message API response data 
+ **/
+public class SmsMessageApiResponseData {
+	private Integer httpStatusCode;
+	private List<SmsMessageDeliveryReportData> data;
+	
+	/** 
+	 * SmsMessageApiResponseData constructor
+	 * 
+	 * @return void 
+	 **/
+	private SmsMessageApiResponseData(Integer httpStatusCode, List<SmsMessageDeliveryReportData> data) {
+		this.httpStatusCode = httpStatusCode;
+		this.data = data;
+	}
+	
+	/** 
+	 * Default SmsMessageApiResponseData constructor 
+	 * 
+	 * @return void
+	 **/
+	protected SmsMessageApiResponseData() {}
+	
+	/** 
+	 * @return an instance of the SmsMessageApiResponseData class
+	 **/
+	public static SmsMessageApiResponseData getInstance(Integer httpStatusCode, List<SmsMessageDeliveryReportData> data) {
+		return new SmsMessageApiResponseData(httpStatusCode, data);
+	}
+
+	/**
+	 * @return the httpStatusCode
+	 */
+	public Integer getHttpStatusCode() {
+		return httpStatusCode;
+	}
+
+	/**
+	 * @return the data
+	 */
+	public List<SmsMessageDeliveryReportData> getData() {
+		return data;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java
new file mode 100644
index 0000000..91471b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java
@@ -0,0 +1,113 @@
+/**
+ * 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.sms.data;
+
+/** 
+ * Immutable data object representing an outbound SMS message delivery report data 
+ **/
+public class SmsMessageDeliveryReportData {
+	private Long id;
+	private String externalId;
+	private String addedOnDate;
+	private String deliveredOnDate;
+	private Integer deliveryStatus;
+	private Boolean hasError;
+	private String errorMessage;
+	
+	/** 
+	 * SmsMessageDeliveryReportData constructor
+	 * 
+	 * @return void 
+	 **/
+	private SmsMessageDeliveryReportData(Long id, String externalId, String addedOnDate, String deliveredOnDate, 
+			Integer deliveryStatus, Boolean hasError, String errorMessage) {
+		this.id = id;
+		this.externalId = externalId;
+		this.addedOnDate = addedOnDate;
+		this.deliveredOnDate = deliveredOnDate;
+		this.deliveryStatus = deliveryStatus;
+		this.hasError = hasError;
+		this.errorMessage = errorMessage;
+	}
+	
+	/** 
+	 * Default SmsMessageDeliveryReportData constructor 
+	 * 
+	 * @return void
+	 **/
+	protected SmsMessageDeliveryReportData() {}
+	
+	/** 
+	 * @return an instance of the SmsMessageDeliveryReportData class
+	 **/
+	public static SmsMessageDeliveryReportData getInstance(Long id, String externalId, String addedOnDate, String deliveredOnDate, 
+			Integer deliveryStatus, Boolean hasError, String errorMessage) {
+		
+		return new SmsMessageDeliveryReportData(id, externalId, addedOnDate, deliveredOnDate, deliveryStatus, hasError, errorMessage);
+	}
+
+	/**
+	 * @return the id
+	 */
+	public Long getId() {
+		return id;
+	}
+
+	/**
+	 * @return the externalId
+	 */
+	public String getExternalId() {
+		return externalId;
+	}
+
+	/**
+	 * @return the addedOnDate
+	 */
+	public String getAddedOnDate() {
+		return addedOnDate;
+	}
+
+	/**
+	 * @return the deliveredOnDate
+	 */
+	public String getDeliveredOnDate() {
+		return deliveredOnDate;
+	}
+
+	/**
+	 * @return the deliveryStatus
+	 */
+	public Integer getDeliveryStatus() {
+		return deliveryStatus;
+	}
+
+	/**
+	 * @return the hasError
+	 */
+	public Boolean getHasError() {
+		return hasError;
+	}
+
+	/**
+	 * @return the errorMessage
+	 */
+	public String getErrorMessage() {
+		return errorMessage;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java
index 377834a..a388bb5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.infrastructure.sms.domain;
 
+import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -26,19 +27,26 @@ import javax.persistence.Entity;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
 import org.apache.fineract.infrastructure.sms.SmsApiConstants;
 import org.apache.fineract.organisation.staff.domain.Staff;
 import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.group.domain.Group;
-import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.joda.time.LocalDate;
 
 @Entity
 @Table(name = "sms_messages_outbound")
 public class SmsMessage extends AbstractPersistableCustom<Long> {
 
+    @Column(name = "external_id", nullable = true)
+    private Long externalId;
+
     @ManyToOne
     @JoinColumn(name = "group_id", nullable = true)
     private Group group;
@@ -51,6 +59,10 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
     @JoinColumn(name = "staff_id", nullable = true)
     private Staff staff;
 
+    @ManyToOne
+    @JoinColumn(name = "campaign_id", nullable = false)
+    private SmsCampaign smsCampaign;
+
     @Column(name = "status_enum", nullable = false)
     private Integer statusType;
 
@@ -60,23 +72,51 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
     @Column(name = "message", nullable = false)
     private String message;
 
-    public static SmsMessage pendingSms(final Group group, final Client client, final Staff staff, final String message,
-            final String mobileNo) {
-        return new SmsMessage(group, client, staff, SmsMessageStatusType.PENDING, message, mobileNo);
+//    @Column(name = "provider_id", nullable = true)
+//    private Long providerId;
+//
+//    @Column(name = "campaign_name", nullable = true)
+//    private String campaignName;
+
+    @Column(name = "submittedon_date", nullable = true)
+    @Temporal(TemporalType.DATE)
+    private Date submittedOnDate;
+
+    @Column(name = "delivered_on_date", nullable = true)
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date deliveredOnDate;
+
+    public static SmsMessage pendingSms(final Long externalId, final Group group, final Client client, final Staff staff,
+            final String message, final String mobileNo, final SmsCampaign smsCampaign) {
+        return new SmsMessage(externalId, group, client, staff, SmsMessageStatusType.PENDING, message, mobileNo, smsCampaign);
+    }
+
+    public static SmsMessage sentSms(final Long externalId, final Group group, final Client client, final Staff staff,
+            final String message, final String mobileNo, final SmsCampaign smsCampaign) {
+        return new SmsMessage(externalId, group, client, staff, SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT, message, mobileNo, smsCampaign);
+    }
+
+    public static SmsMessage instance(Long externalId, final Group group, final Client client, final Staff staff,
+            final SmsMessageStatusType statusType, final String message, final String mobileNo, final SmsCampaign smsCampaign) {
+
+        return new SmsMessage(externalId, group, client, staff, statusType, message, mobileNo, smsCampaign);
     }
 
     protected SmsMessage() {
         //
     }
 
-    private SmsMessage(final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType,
-            final String message, final String mobileNo) {
+    private SmsMessage(Long externalId, final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType,
+            final String message, final String mobileNo, final SmsCampaign smsCampaign) {
+        this.externalId = externalId;
         this.group = group;
         this.client = client;
         this.staff = staff;
         this.statusType = statusType.getValue();
         this.mobileNo = mobileNo;
         this.message = message;
+        this.smsCampaign = smsCampaign;
+        this.submittedOnDate = LocalDate.now().toDate();
     }
 
     public Map<String, Object> update(final JsonCommand command) {
@@ -91,4 +131,56 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
 
         return actualChanges;
     }
+
+    public Long getExternalId() {
+        return this.externalId;
+    }
+
+    public SmsCampaign getSmsCampaign() {
+        return this.smsCampaign;
+    }
+
+    public Group getGroup() {
+        return group;
+    }
+
+    public Client getClient() {
+        return client;
+    }
+
+    public Staff getStaff() {
+        return staff;
+    }
+
+    public Integer getStatusType() {
+        return statusType;
+    }
+
+    public String getMobileNo() {
+        return mobileNo;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setExternalId(final Long externalId) {
+        this.externalId = externalId;
+    }
+
+    public void setStatusType(final Integer statusType) {
+        this.statusType = statusType;
+    }
+
+    public Date getSubmittedOnDate() {
+        return this.submittedOnDate;
+    }
+
+    public Date getDeliveredOnDate() {
+        return this.deliveredOnDate;
+    }
+
+    public void setDeliveredOnDate(final Date deliveredOnDate) {
+        this.deliveredOnDate = deliveredOnDate;
+    }
 }
\ No newline at end of file