You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by na...@apache.org on 2017/11/20 10:29:17 UTC

[1/4] fineract git commit: notification sms

Repository: fineract
Updated Branches:
  refs/heads/develop fdac5fa6e -> e23ff94dd


http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/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 e589f00..c496604 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
@@ -66,7 +66,7 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
     @Column(name = "status_enum", nullable = false)
     private Integer statusType;
 
-    @Column(name = "mobile_no", nullable = false, length = 50)
+    @Column(name = "mobile_no", nullable = true, length = 50)
     private String mobileNo;
 
     @Column(name = "message", nullable = false)
@@ -84,22 +84,25 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
 
     @Column(name = "delivered_on_date", nullable = true)
     @Temporal(TemporalType.TIMESTAMP)
-    private Date deliveredOnDate;
+    private Date deliveredOnDate; 
+    
+    @Column(name = "is_notification", nullable = true)
+    private boolean isNotification;
 
     public static SmsMessage pendingSms(final String 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);
+            final String message, final String mobileNo, final SmsCampaign smsCampaign, final boolean isNotification) {
+        return new SmsMessage(externalId, group, client, staff, SmsMessageStatusType.PENDING, message, mobileNo, smsCampaign, isNotification);
     }
 
     public static SmsMessage sentSms(final String 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);
+            final String message, final String mobileNo, final SmsCampaign smsCampaign, final boolean isNotification) {
+        return new SmsMessage(externalId, group, client, staff, SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT, message, mobileNo, smsCampaign, isNotification);
     }
 
     public static SmsMessage instance(String externalId, final Group group, final Client client, final Staff staff,
-            final SmsMessageStatusType statusType, final String message, final String mobileNo, final SmsCampaign smsCampaign) {
+            final SmsMessageStatusType statusType, final String message, final String mobileNo, final SmsCampaign smsCampaign, final boolean isNotification) {
 
-        return new SmsMessage(externalId, group, client, staff, statusType, message, mobileNo, smsCampaign);
+        return new SmsMessage(externalId, group, client, staff, statusType, message, mobileNo, smsCampaign, isNotification);
     }
 
     protected SmsMessage() {
@@ -107,7 +110,7 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
     }
 
     private SmsMessage(String externalId, final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType,
-            final String message, final String mobileNo, final SmsCampaign smsCampaign) {
+            final String message, final String mobileNo, final SmsCampaign smsCampaign, final boolean isNotification) {
         this.externalId = externalId;
         this.group = group;
         this.client = client;
@@ -117,6 +120,7 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
         this.message = message;
         this.smsCampaign = smsCampaign;
         this.submittedOnDate = LocalDate.now().toDate();
+        this.isNotification = isNotification;
     }
 
     public Map<String, Object> update(final JsonCommand command) {
@@ -183,4 +187,14 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
     public void setDeliveredOnDate(final Date deliveredOnDate) {
         this.deliveredOnDate = deliveredOnDate;
     }
+
+	public boolean isNotification() {
+		return this.isNotification;
+	}
+
+	public void setNotification(boolean isNotification) {
+		this.isNotification = isNotification;
+	}
+    
+    
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
index c879ffb..3259133 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
@@ -71,10 +71,12 @@ public class SmsMessageAssembler {
         }
 
         SmsCampaign smsCampaign = null;
+        boolean isNotification = false;
         if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.campaignIdParamName, element)) {
             final Long campaignId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.campaignIdParamName, element);
             smsCampaign = this.smsCampaignRepository.findOne(campaignId);
             if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); }
+            isNotification = smsCampaign.isNotification();
         }
 
         Client client = null;
@@ -93,7 +95,7 @@ public class SmsMessageAssembler {
 
         final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
 
-        return SmsMessage.pendingSms(externalId, group, client, staff, message, mobileNo, smsCampaign);
+        return SmsMessage.pendingSms(externalId, group, client, staff, message, mobileNo, smsCampaign, isNotification);
     }
 
     public SmsMessage assembleFromResourceId(final Long resourceId) {

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
index 373af1d..e2e998a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
@@ -37,6 +37,7 @@ import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFail
 import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
 import org.apache.fineract.infrastructure.core.service.Page;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.gcm.service.NotificationSenderService;
 import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
 import org.apache.fineract.infrastructure.sms.data.SmsMessageApiQueueResourceData;
@@ -76,6 +77,7 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob
     private  ExecutorService genericExecutorService ;
     private ExecutorService triggeredExecutorService ;
     private final SmsConfigUtils smsConfigUtils ;
+    private final NotificationSenderService notificationSenderService;
     
     
     /**
@@ -83,10 +85,11 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob
      **/
     @Autowired
     public SmsMessageScheduledJobServiceImpl(SmsMessageRepository smsMessageRepository, SmsReadPlatformService smsReadPlatformService,
-            final SmsConfigUtils smsConfigUtils) {
+            final SmsConfigUtils smsConfigUtils, final NotificationSenderService notificationSenderService) {
         this.smsMessageRepository = smsMessageRepository;
         this.smsReadPlatformService = smsReadPlatformService;
         this.smsConfigUtils = smsConfigUtils ;
+        this.notificationSenderService = notificationSenderService;
     }
 
     @PostConstruct
@@ -110,6 +113,7 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob
             org.springframework.data.domain.Page<SmsMessage> pendingMessages = this.smsMessageRepository.findByStatusType(
                     SmsMessageStatusType.PENDING.getValue(), pageRequest);
             List<SmsMessage> toSaveMessages = new ArrayList<>() ;
+            List<SmsMessage> toSendNotificationMessages = new ArrayList<>() ;
             try {
 
                 if (pendingMessages.getContent().size() > 0) {
@@ -118,18 +122,26 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob
                     Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas = new ArrayList<>();
                     while (pendingMessageIterator.hasNext()) {
                         SmsMessage smsData = pendingMessageIterator.next();
-
-                        SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsData.getId(),
-                                tenantIdentifier, null, null, smsData.getMobileNo(), smsData.getMessage(), smsData.getSmsCampaign()
-                                        .getProviderId());
-                        apiQueueResourceDatas.add(apiQueueResourceData);
-                        smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
-                        toSaveMessages.add(smsData) ;
+                        if(smsData.isNotification()){
+                        	smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
+                        	toSendNotificationMessages.add(smsData);
+                        }else{
+                        	SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsData.getId(),
+                                    tenantIdentifier, null, null, smsData.getMobileNo(), smsData.getMessage(), smsData.getSmsCampaign()
+                                            .getProviderId());
+                            apiQueueResourceDatas.add(apiQueueResourceData);
+                            smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
+                            toSaveMessages.add(smsData) ;
+                        }
+                    }
+                    if(toSaveMessages.size()>0){
+                    	this.smsMessageRepository.save(toSaveMessages);
+                        this.smsMessageRepository.flush();
+                        this.genericExecutorService.execute(new SmsTask(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas));
+                    }                    
+                    if(!toSendNotificationMessages.isEmpty()){
+                    	this.notificationSenderService.sendNotification(toSendNotificationMessages);
                     }
-                    this.smsMessageRepository.save(toSaveMessages);
-                    this.smsMessageRepository.flush();
-                    this.genericExecutorService.execute(new SmsTask(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas));
-
 //                    new MyThread(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas).start();
                 }
             } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
index 3827307..0a8602e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
@@ -289,6 +289,12 @@ final public class ClientData implements Comparable<ClientData> {
                 clientNonPerson, clientLegalFormOptions,familyMemberOptions, legalForm,null,null, null, isStaff);
 
     }
+    
+    public static ClientData instance(final Long id, final String displayName){
+    	 final Long officeId = null;
+    	 final String officeName = null;
+    	 return lookup(id, displayName, officeId, officeName);
+    }
 
     public static ClientData instance(final String accountNo, final EnumOptionData status, final CodeValueData subStatus,
             final Long officeId, final String officeName, final Long transferToOfficeId, final String transferToOfficeName, final Long id,

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
index 9952dcf..8b0b6a4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
@@ -203,8 +203,9 @@ public class SelfServiceRegistrationWritePlatformServiceImpl implements SelfServ
         Group group = null;
         Staff staff = null;
         SmsCampaign smsCampaign = null;
+        boolean isNotification = false;
         SmsMessage smsMessage = SmsMessage.instance(externalId, group, selfServiceRegistration.getClient(), staff,
-                SmsMessageStatusType.PENDING, message, selfServiceRegistration.getMobileNumber(), smsCampaign);
+                SmsMessageStatusType.PENDING, message, selfServiceRegistration.getMobileNumber(), smsCampaign, isNotification);
         this.smsMessageRepository.save(smsMessage);
         this.smsMessageScheduledJobService.sendTriggeredMessage(new ArrayList<>(Arrays.asList(smsMessage)), providerId);
     }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/resources/sql/migrations/core_db/V336__sms_campaign_notification.sql
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V336__sms_campaign_notification.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V336__sms_campaign_notification.sql
new file mode 100644
index 0000000..37ab496
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V336__sms_campaign_notification.sql
@@ -0,0 +1,48 @@
+--
+-- 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.
+--
+
+CREATE TABLE `client_device_registration` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`client_id` BIGINT(20) NOT NULL,
+	`updatedon_date` DATETIME NOT NULL,
+	`registration_id` VARCHAR(255) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `registration_key_client_device_registration` (`registration_id`),
+	UNIQUE INDEX `client_key_client_device_registration` (`client_id`),
+	INDEX `FK1_client_device_registration_m_client` (`client_id`),
+	CONSTRAINT `FK1_client_device_registration_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+);
+
+INSERT INTO `c_external_service` (`name`) VALUES ('NOTIFICATION');
+
+INSERT INTO `c_external_service_properties` (`name`, `value`, `external_service_id`) VALUES ('server_key', 
+'AAAAToBmqQQ:APA91bEodkE12CwFl8VHqanUbeJYg1E05TiheVz59CZZYrBnCq3uM40UYhHfdP-JfeTQ0L0zoLqS8orjvW_ze0_VF8DSuyyqkrDibflhtUainsI0lwgVz5u1YP3q3c3erqjlySEuRShS', 
+(select id from `c_external_service` where name ='NOTIFICATION')
+),('gcm_end_point','https://gcm-http.googleapis.com/gcm/send',(select id from `c_external_service` where name ='NOTIFICATION')
+),('fcm_end_point','https://fcm.googleapis.com/fcm/send',(select id from `c_external_service` where name ='NOTIFICATION')
+);
+
+ALTER TABLE sms_campaign 
+MODIFY COLUMN provider_id BIGINT(20) NULL DEFAULT NULL,
+ADD COLUMN is_notification TINYINT(1) NULL DEFAULT '0'; 
+
+ALTER TABLE sms_messages_outbound 
+MODIFY COLUMN mobile_no VARCHAR(50) NULL DEFAULT NULL,
+ADD COLUMN is_notification TINYINT(1) NOT NULL DEFAULT '0'
+


[4/4] fineract git commit: Merge branch 'PR421' into develop

Posted by na...@apache.org.
Merge branch 'PR421' into develop


Project: http://git-wip-us.apache.org/repos/asf/fineract/repo
Commit: http://git-wip-us.apache.org/repos/asf/fineract/commit/e23ff94d
Tree: http://git-wip-us.apache.org/repos/asf/fineract/tree/e23ff94d
Diff: http://git-wip-us.apache.org/repos/asf/fineract/diff/e23ff94d

Branch: refs/heads/develop
Commit: e23ff94dde0d45f88d8a5940634157e771263bdf
Parents: fdac5fa 8f30c21
Author: Nazeer Hussain Shaik <na...@confluxtechnologies.com>
Authored: Mon Nov 20 15:58:35 2017 +0530
Committer: Nazeer Hussain Shaik <na...@confluxtechnologies.com>
Committed: Mon Nov 20 15:58:35 2017 +0530

----------------------------------------------------------------------
 .../campaigns/constants/CampaignType.java       |  12 +-
 .../sms/constants/SmsCampaignEnumerations.java  |   3 +
 .../campaigns/sms/data/SmsCampaignData.java     |  16 +-
 .../campaigns/sms/domain/SmsCampaign.java       |  36 +-
 .../sms/domain/SmsCampaignRepository.java       |   2 +
 .../sms/serialization/SmsCampaignValidator.java |  38 +-
 .../service/SmsCampaignDomainServiceImpl.java   |  23 +-
 ...CampaignDropdownReadPlatformServiceImpl.java |   3 -
 .../SmsCampaignReadPlatformServiceImpl.java     |   7 +-
 .../SmsCampaignWritePlatformServiceJpaImpl.java |  49 +-
 ...ropertiesCommandFromApiJsonDeserializer.java |   6 +
 .../service/ExternalServicesConstants.java      |  36 +
 ...alServicesPropertiesReadPlatformService.java |   3 +
 ...rvicesPropertiesReadPlatformServiceImpl.java |  35 +
 ...ExternalServicesReadPlatformServiceImpl.java |   4 +
 .../infrastructure/gcm/GcmConstants.java        | 242 ++++++
 .../gcm/api/DeviceRegistrationApiConstants.java |  25 +
 .../gcm/api/DeviceRegistrationApiResource.java  | 160 ++++
 .../gcm/domain/DeviceRegistration.java          |  85 ++
 .../gcm/domain/DeviceRegistrationData.java      |  47 ++
 .../domain/DeviceRegistrationRepository.java    |  36 +
 .../DeviceRegistrationRepositoryWrapper.java    |  61 ++
 .../infrastructure/gcm/domain/Message.java      | 317 +++++++
 .../gcm/domain/MulticastResult.java             | 151 ++++
 .../infrastructure/gcm/domain/Notification.java | 330 ++++++++
 .../domain/NotificationConfigurationData.java   |  49 ++
 .../infrastructure/gcm/domain/Result.java       | 187 +++++
 .../infrastructure/gcm/domain/Sender.java       | 832 +++++++++++++++++++
 .../DeviceRegistrationNotFoundException.java    |  38 +
 .../gcm/exception/InvalidRequestException.java  |  66 ++
 .../DeviceRegistrationReadPlatformService.java  |  32 +
 ...viceRegistrationReadPlatformServiceImpl.java | 125 +++
 .../DeviceRegistrationWritePlatformService.java |  30 +
 ...iceRegistrationWritePlatformServiceImpl.java | 123 +++
 .../gcm/service/NotificationSenderService.java  | 133 +++
 .../infrastructure/sms/domain/SmsMessage.java   |  32 +-
 .../sms/domain/SmsMessageAssembler.java         |   4 +-
 .../SmsMessageScheduledJobServiceImpl.java      |  36 +-
 .../portfolio/client/data/ClientData.java       |   6 +
 ...iceRegistrationWritePlatformServiceImpl.java |   3 +-
 .../core_db/V336__sms_campaign_notification.sql |  48 ++
 41 files changed, 3409 insertions(+), 62 deletions(-)
----------------------------------------------------------------------



[2/4] fineract git commit: notification sms

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Message.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Message.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Message.java
new file mode 100644
index 0000000..34234dc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Message.java
@@ -0,0 +1,317 @@
+/**
+ * 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.gcm.domain;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.gcm.GcmConstants;
+
+/**
+ * GCM message.
+ *
+ * <p>
+ * Instances of this class are immutable and should be created using a
+ * {@link Builder}. Examples:
+ *
+ * <strong>Simplest message:</strong>
+ * 
+ * <pre>
+ * <code>
+ * Message message = new Message.Builder().build();
+ * </pre>
+ * 
+ * </code>
+ *
+ * <strong>Message with optional attributes:</strong>
+ * 
+ * <pre>
+ * <code>
+ * Message message = new Message.Builder()
+ *    .collapseKey(collapseKey)
+ *    .timeToLive(3)
+ *    .delayWhileIdle(true)
+ *    .dryRun(true)
+ *    .restrictedPackageName(restrictedPackageName)
+ *    .build();
+ * </pre>
+ * 
+ * </code>
+ *
+ * <strong>Message with optional attributes and payload data:</strong>
+ * 
+ * <pre>
+ * <code>
+ * Message message = new Message.Builder()
+ *    .priority("normal")
+ *    .collapseKey(collapseKey)
+ *    .timeToLive(3)
+ *    .delayWhileIdle(true)
+ *    .dryRun(true)
+ *    .restrictedPackageName(restrictedPackageName)
+ *    .addData("key1", "value1")
+ *    .addData("key2", "value2")
+ *    .build();
+ * </pre>
+ * 
+ * </code>
+ */
+public final class Message implements Serializable {
+
+	private final String collapseKey;
+	private final Boolean delayWhileIdle;
+	private final Integer timeToLive;
+	private final Map<String, String> data;
+	private final Boolean dryRun;
+	private final String restrictedPackageName;
+	private final String priority;
+	private final Boolean contentAvailable;
+	private final Notification notification;
+
+	public enum Priority {
+		NORMAL, HIGH
+	}
+
+	public static final class Builder {
+
+		private final Map<String, String> data;
+
+		// optional parameters
+		private String collapseKey;
+		private Boolean delayWhileIdle;
+		private Integer timeToLive;
+		private Boolean dryRun;
+		private String restrictedPackageName;
+		private String priority;
+		private Boolean contentAvailable;
+		private Notification notification;
+
+		public Builder() {
+			this.data = new LinkedHashMap<>();
+		}
+
+		/**
+		 * Sets the collapseKey property.
+		 */
+		public Builder collapseKey(String value) {
+			collapseKey = value;
+			return this;
+		}
+
+		/**
+		 * Sets the delayWhileIdle property (default value is {@literal false}).
+		 */
+		public Builder delayWhileIdle(boolean value) {
+			delayWhileIdle = value;
+			return this;
+		}
+
+		/**
+		 * Sets the time to live, in seconds.
+		 */
+		public Builder timeToLive(int value) {
+			timeToLive = value;
+			return this;
+		}
+
+		/**
+		 * Adds a key/value pair to the payload data.
+		 */
+		public Builder addData(String key, String value) {
+			data.put(key, value);
+			return this;
+		}
+
+		/**
+		 * Sets the dryRun property (default value is {@literal false}).
+		 */
+		public Builder dryRun(boolean value) {
+			dryRun = value;
+			return this;
+		}
+
+		/**
+		 * Sets the restrictedPackageName property.
+		 */
+		public Builder restrictedPackageName(String value) {
+			restrictedPackageName = value;
+			return this;
+		}
+
+		/**
+		 * Sets the priority property.
+		 */
+		public Builder priority(Priority value) {
+			switch (value) {
+			case NORMAL:
+				priority = GcmConstants.MESSAGE_PRIORITY_NORMAL;
+				break;
+			case HIGH:
+				priority = GcmConstants.MESSAGE_PRIORITY_HIGH;
+				break;
+			}
+			return this;
+		}
+
+		/**
+		 * Sets the notification property.
+		 */
+		public Builder notification(Notification value) {
+			notification = value;
+			return this;
+		}
+
+		/**
+		 * Sets the contentAvailable property
+		 */
+		public Builder contentAvailable(Boolean value) {
+			contentAvailable = value;
+			return this;
+		}
+
+		public Message build() {
+			return new Message(this);
+		}
+
+	}
+
+	private Message(Builder builder) {
+		collapseKey = builder.collapseKey;
+		delayWhileIdle = builder.delayWhileIdle;
+		data = Collections.unmodifiableMap(builder.data);
+		timeToLive = builder.timeToLive;
+		dryRun = builder.dryRun;
+		restrictedPackageName = builder.restrictedPackageName;
+		priority = builder.priority;
+		contentAvailable = builder.contentAvailable;
+		notification = builder.notification;
+	}
+
+	/**
+	 * Gets the collapse key.
+	 */
+	public String getCollapseKey() {
+		return collapseKey;
+	}
+
+	/**
+	 * Gets the delayWhileIdle flag.
+	 */
+	public Boolean isDelayWhileIdle() {
+		return delayWhileIdle;
+	}
+
+	/**
+	 * Gets the time to live (in seconds).
+	 */
+	public Integer getTimeToLive() {
+		return timeToLive;
+	}
+
+	/**
+	 * Gets the dryRun flag.
+	 */
+	public Boolean isDryRun() {
+		return dryRun;
+	}
+
+	/**
+	 * Gets the restricted package name.
+	 */
+	public String getRestrictedPackageName() {
+		return restrictedPackageName;
+	}
+
+	/**
+	 * Gets the message priority value.
+	 */
+	public String getPriority() {
+		return priority;
+	}
+
+	/**
+	 * Gets the contentAvailable value
+	 */
+	public Boolean getContentAvailable() {
+		return contentAvailable;
+	}
+
+	/**
+	 * Gets the payload data, which is immutable.
+	 */
+	public Map<String, String> getData() {
+		return data;
+	}
+
+	/**
+	 * Gets notification payload, which is immutable.
+	 */
+	public Notification getNotification() {
+		return notification;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder("Message(");
+		if (priority != null) {
+			builder.append("priority=").append(priority).append(", ");
+		}
+		if (contentAvailable != null) {
+			builder.append("contentAvailable=").append(contentAvailable)
+					.append(", ");
+		}
+		if (collapseKey != null) {
+			builder.append("collapseKey=").append(collapseKey).append(", ");
+		}
+		if (timeToLive != null) {
+			builder.append("timeToLive=").append(timeToLive).append(", ");
+		}
+		if (delayWhileIdle != null) {
+			builder.append("delayWhileIdle=").append(delayWhileIdle)
+					.append(", ");
+		}
+		if (dryRun != null) {
+			builder.append("dryRun=").append(dryRun).append(", ");
+		}
+		if (restrictedPackageName != null) {
+			builder.append("restrictedPackageName=")
+					.append(restrictedPackageName).append(", ");
+		}
+		if (notification != null) {
+			builder.append("notification: ").append(notification).append(", ");
+		}
+		if (!data.isEmpty()) {
+			builder.append("data: {");
+			for (Map.Entry<String, String> entry : data.entrySet()) {
+				builder.append(entry.getKey()).append("=")
+						.append(entry.getValue()).append(",");
+			}
+			builder.delete(builder.length() - 1, builder.length());
+			builder.append("}");
+		}
+		if (builder.charAt(builder.length() - 1) == ' ') {
+			builder.delete(builder.length() - 2, builder.length());
+		}
+		builder.append(")");
+		return builder.toString();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/MulticastResult.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/MulticastResult.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/MulticastResult.java
new file mode 100644
index 0000000..c58de9f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/MulticastResult.java
@@ -0,0 +1,151 @@
+/**
+ * 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.gcm.domain;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Result of a GCM multicast message request .
+ */
+public final class MulticastResult implements Serializable {
+
+	private final int success;
+	private final int failure;
+	private final int canonicalIds;
+	private final long multicastId;
+	private final List<Result> results;
+	private final List<Long> retryMulticastIds;
+
+	public static final class Builder {
+
+		private final List<Result> results = new ArrayList<>();
+
+		// required parameters
+		private final int success;
+		private final int failure;
+		private final int canonicalIds;
+		private final long multicastId;
+
+		// optional parameters
+		private List<Long> retryMulticastIds;
+
+		public Builder(int success, int failure, int canonicalIds,
+				long multicastId) {
+			this.success = success;
+			this.failure = failure;
+			this.canonicalIds = canonicalIds;
+			this.multicastId = multicastId;
+		}
+
+		public Builder addResult(Result result) {
+			results.add(result);
+			return this;
+		}
+
+		public Builder retryMulticastIds(List<Long> retryMulticastIds) {
+			this.retryMulticastIds = retryMulticastIds;
+			return this;
+		}
+
+		public MulticastResult build() {
+			return new MulticastResult(this);
+		}
+	}
+
+	private MulticastResult(Builder builder) {
+		success = builder.success;
+		failure = builder.failure;
+		canonicalIds = builder.canonicalIds;
+		multicastId = builder.multicastId;
+		results = Collections.unmodifiableList(builder.results);
+		List<Long> tmpList = builder.retryMulticastIds;
+		if (tmpList == null) {
+			tmpList = Collections.emptyList();
+		}
+		retryMulticastIds = Collections.unmodifiableList(tmpList);
+	}
+
+	/**
+	 * Gets the multicast id.
+	 */
+	public long getMulticastId() {
+		return multicastId;
+	}
+
+	/**
+	 * Gets the number of successful messages.
+	 */
+	public int getSuccess() {
+		return success;
+	}
+
+	/**
+	 * Gets the total number of messages sent, regardless of the status.
+	 */
+	public int getTotal() {
+		return success + failure;
+	}
+
+	/**
+	 * Gets the number of failed messages.
+	 */
+	public int getFailure() {
+		return failure;
+	}
+
+	/**
+	 * Gets the number of successful messages that also returned a canonical
+	 * registration id.
+	 */
+	public int getCanonicalIds() {
+		return canonicalIds;
+	}
+
+	/**
+	 * Gets the results of each individual message, which is immutable.
+	 */
+	public List<Result> getResults() {
+		return results;
+	}
+
+	/**
+	 * Gets additional ids if more than one multicast message was sent.
+	 */
+	public List<Long> getRetryMulticastIds() {
+		return retryMulticastIds;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder("MulticastResult(")
+				.append("multicast_id=").append(multicastId).append(",")
+				.append("total=").append(getTotal()).append(",")
+				.append("success=").append(success).append(",")
+				.append("failure=").append(failure).append(",")
+				.append("canonical_ids=").append(canonicalIds).append(",");
+		if (!results.isEmpty()) {
+			builder.append("results: " + results);
+		}
+		return builder.toString();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Notification.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Notification.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Notification.java
new file mode 100644
index 0000000..589e772
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Notification.java
@@ -0,0 +1,330 @@
+/**
+ * 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.gcm.domain;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * GCM message notification part.
+ *
+ * <p>
+ * Instances of this class are immutable and should be created using a
+ * {@link Builder}. Examples:
+ *
+ * <strong>Simplest notification:</strong>
+ * 
+ * <pre>
+ * <code>
+ * Notification notification = new Notification.Builder("myicon").build();
+ * </pre>
+ * 
+ * </code>
+ *
+ * <strong>Notification with optional attributes:</strong>
+ * 
+ * <pre>
+ * <code>
+ * Notification notification = new Notification.Builder("myicon")
+ *    .title("Hello world!")
+ *    .body("Here is a more detailed description")
+ *    .build();
+ * </pre>
+ * 
+ * </code>
+ */
+public final class Notification implements Serializable {
+
+	private final String title;
+	private final String body;
+	private final String icon;
+	private final String sound;
+	private final Integer badge;
+	private final String tag;
+	private final String color;
+	private final String clickAction;
+	private final String bodyLocKey;
+	private final List<String> bodyLocArgs;
+	private final String titleLocKey;
+	private final List<String> titleLocArgs;
+
+	public static final class Builder {
+
+		// required parameters
+		private final String icon;
+
+		// optional parameters
+		private String title;
+		private String body;
+		private String sound;
+		private Integer badge;
+		private String tag;
+		private String color;
+		private String clickAction;
+		private String bodyLocKey;
+		private List<String> bodyLocArgs;
+		private String titleLocKey;
+		private List<String> titleLocArgs;
+
+		public Builder(String icon) {
+			this.icon = icon;
+			this.sound = "default"; // the only currently supported value
+		}
+
+		/**
+		 * Sets the title property.
+		 */
+		public Builder title(String value) {
+			title = value;
+			return this;
+		}
+
+		/**
+		 * Sets the body property.
+		 */
+		public Builder body(String value) {
+			body = value;
+			return this;
+		}
+
+		/**
+		 * Sets the sound property (default value is {@literal default}).
+		 */
+		public Builder sound(String value) {
+			sound = value;
+			return this;
+		}
+
+		/**
+		 * Sets the badge property.
+		 */
+		public Builder badge(int value) {
+			badge = value;
+			return this;
+		}
+
+		/**
+		 * Sets the tag property.
+		 */
+		public Builder tag(String value) {
+			tag = value;
+			return this;
+		}
+
+		/**
+		 * Sets the color property in {@literal #rrggbb} format.
+		 */
+		public Builder color(String value) {
+			color = value;
+			return this;
+		}
+
+		/**
+		 * Sets the click action property.
+		 */
+		public Builder clickAction(String value) {
+			clickAction = value;
+			return this;
+		}
+
+		/**
+		 * Sets the body localization key property.
+		 */
+		public Builder bodyLocKey(String value) {
+			bodyLocKey = value;
+			return this;
+		}
+
+		/**
+		 * Sets the body localization values property.
+		 */
+		public Builder bodyLocArgs(List<String> value) {
+			bodyLocArgs = Collections.unmodifiableList(value);
+			return this;
+		}
+
+		/**
+		 * Sets the title localization key property.
+		 */
+		public Builder titleLocKey(String value) {
+			titleLocKey = value;
+			return this;
+		}
+
+		/**
+		 * Sets the title localization values property.
+		 */
+		public Builder titleLocArgs(List<String> value) {
+			titleLocArgs = Collections.unmodifiableList(value);
+			return this;
+		}
+
+		public Notification build() {
+			return new Notification(this);
+		}
+
+	}
+
+	private Notification(Builder builder) {
+		title = builder.title;
+		body = builder.body;
+		icon = builder.icon;
+		sound = builder.sound;
+		badge = builder.badge;
+		tag = builder.tag;
+		color = builder.color;
+		clickAction = builder.clickAction;
+		bodyLocKey = builder.bodyLocKey;
+		bodyLocArgs = builder.bodyLocArgs;
+		titleLocKey = builder.titleLocKey;
+		titleLocArgs = builder.titleLocArgs;
+	}
+
+	/**
+	 * Gets the title.
+	 */
+	public String getTitle() {
+		return title;
+	}
+
+	/**
+	 * Gets the body.
+	 */
+	public String getBody() {
+		return body;
+	}
+
+	/**
+	 * Gets the icon.
+	 */
+	public String getIcon() {
+		return icon;
+	}
+
+	/**
+	 * Gets the sound.
+	 */
+	public String getSound() {
+		return sound;
+	}
+
+	/**
+	 * Gets the badge.
+	 */
+	public Integer getBadge() {
+		return badge;
+	}
+
+	/**
+	 * Gets the tag.
+	 */
+	public String getTag() {
+		return tag;
+	}
+
+	/**
+	 * Gets the color.
+	 */
+	public String getColor() {
+		return color;
+	}
+
+	/**
+	 * Gets the click action.
+	 */
+	public String getClickAction() {
+		return clickAction;
+	}
+
+	/**
+	 * Gets the body localization key.
+	 */
+	public String getBodyLocKey() {
+		return bodyLocKey;
+	}
+
+	/**
+	 * Gets the body localization values list, which is immutable.
+	 */
+	public List<String> getBodyLocArgs() {
+		return bodyLocArgs;
+	}
+
+	/**
+	 * Gets the title localization key.
+	 */
+	public String getTitleLocKey() {
+		return titleLocKey;
+	}
+
+	/**
+	 * Gets the title localization values list, which is immutable.
+	 */
+	public List<String> getTitleLocArgs() {
+		return titleLocArgs;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder("Notification(");
+		if (title != null) {
+			builder.append("title=").append(title).append(", ");
+		}
+		if (body != null) {
+			builder.append("body=").append(body).append(", ");
+		}
+		if (icon != null) {
+			builder.append("icon=").append(icon).append(", ");
+		}
+		if (sound != null) {
+			builder.append("sound=").append(sound).append(", ");
+		}
+		if (badge != null) {
+			builder.append("badge=").append(badge).append(", ");
+		}
+		if (tag != null) {
+			builder.append("tag=").append(tag).append(", ");
+		}
+		if (color != null) {
+			builder.append("color=").append(color).append(", ");
+		}
+		if (clickAction != null) {
+			builder.append("clickAction=").append(clickAction).append(", ");
+		}
+		if (bodyLocKey != null) {
+			builder.append("bodyLocKey=").append(bodyLocKey).append(", ");
+		}
+		if (bodyLocArgs != null) {
+			builder.append("bodyLocArgs=").append(bodyLocArgs).append(", ");
+		}
+		if (titleLocKey != null) {
+			builder.append("titleLocKey=").append(titleLocKey).append(", ");
+		}
+		if (titleLocArgs != null) {
+			builder.append("titleLocArgs=").append(titleLocArgs).append(", ");
+		}
+		if (builder.charAt(builder.length() - 1) == ' ') {
+			builder.delete(builder.length() - 2, builder.length());
+		}
+		builder.append(")");
+		return builder.toString();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/NotificationConfigurationData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/NotificationConfigurationData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/NotificationConfigurationData.java
new file mode 100644
index 0000000..2d4acc9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/NotificationConfigurationData.java
@@ -0,0 +1,49 @@
+/**
+ * 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.gcm.domain;
+
+public class NotificationConfigurationData {
+	
+	private final Long id;
+    private final String serverKey;
+    private final String gcmEndPoint;
+    private final String fcmEndPoint;
+	public NotificationConfigurationData(Long id, String serverKey,final String gcmEndPoint,final String fcmEndPoint) {
+		this.id = id;
+		this.serverKey = serverKey;
+		this.gcmEndPoint = gcmEndPoint;
+		this.fcmEndPoint = fcmEndPoint;
+	}
+	public Long getId() {
+		return id;
+	}
+	public String getServerKey() {
+		return serverKey;
+	}
+	
+	public String getGcmEndPoint() {
+		return gcmEndPoint;
+	}
+	public String getFcmEndPoint() {
+		return fcmEndPoint;
+	}
+    
+    
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Result.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Result.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Result.java
new file mode 100644
index 0000000..76aafa8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Result.java
@@ -0,0 +1,187 @@
+/**
+ * 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.gcm.domain;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Result of a GCM message request that returned HTTP status code 200.
+ *
+ * <p>
+ * If the message is successfully created, the {@link #getMessageId()} returns
+ * the message id and {@link #getErrorCodeName()} returns {@literal null};
+ * otherwise, {@link #getMessageId()} returns {@literal null} and
+ * {@link #getErrorCodeName()} returns the code of the error.
+ *
+ * <p>
+ * There are cases when a request is accept and the message successfully
+ * created, but GCM has a canonical registration id for that device. In this
+ * case, the server should update the registration id to avoid rejected requests
+ * in the future.
+ * 
+ * <p>
+ * In a nutshell, the workflow to handle a result is:
+ * 
+ * <pre>
+ *   - Call {@link #getMessageId()}:
+ *     - {@literal null} means error, call {@link #getErrorCodeName()}
+ *     - non-{@literal null} means the message was created:
+ *       - Call {@link #getCanonicalRegistrationId()}
+ *         - if it returns {@literal null}, do nothing.
+ *         - otherwise, update the server datastore with the new id.
+ * </pre>
+ */
+public final class Result implements Serializable {
+
+	private final String messageId;
+	private final String canonicalRegistrationId;
+	private final String errorCode;
+	private final Integer success;
+	private final Integer failure;
+	private final List<String> failedRegistrationIds;
+	private final int status;
+
+	public static final class Builder {
+
+		// optional parameters
+		private String messageId;
+		private String canonicalRegistrationId;
+		private String errorCode;
+		private Integer success;
+		private Integer failure;
+		private List<String> failedRegistrationIds;
+		private int status;
+
+		public Builder canonicalRegistrationId(String value) {
+			canonicalRegistrationId = value;
+			return this;
+		}
+
+		public Builder messageId(String value) {
+			messageId = value;
+			return this;
+		}
+
+		public Builder errorCode(String value) {
+			errorCode = value;
+			return this;
+		}
+
+		public Builder success(Integer value) {
+			success = value;
+			return this;
+		}
+
+		public Builder failure(Integer value) {
+			failure = value;
+			return this;
+		}
+		
+		public Builder status(int value) {
+			status = value;
+			return this;
+		}
+
+		public Builder failedRegistrationIds(List<String> value) {
+			failedRegistrationIds = value;
+			return this;
+		}
+
+		public Result build() {
+			return new Result(this);
+		}
+	}
+
+	private Result(Builder builder) {
+		canonicalRegistrationId = builder.canonicalRegistrationId;
+		messageId = builder.messageId;
+		errorCode = builder.errorCode;
+		success = builder.success;
+		failure = builder.failure;
+		failedRegistrationIds = builder.failedRegistrationIds;
+		status = builder.status;
+	}
+
+	/**
+	 * Gets the message id, if any.
+	 */
+	public String getMessageId() {
+		return messageId;
+	}
+
+	/**
+	 * Gets the canonical registration id, if any.
+	 */
+	public String getCanonicalRegistrationId() {
+		return canonicalRegistrationId;
+	}
+
+	/**
+	 * Gets the error code, if any.
+	 */
+	public String getErrorCodeName() {
+		return errorCode;
+	}
+
+	public Integer getSuccess() {
+		return success;
+	}
+
+	public Integer getFailure() {
+		return failure;
+	}
+
+	public List<String> getFailedRegistrationIds() {
+		return failedRegistrationIds;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder("[");
+		if (messageId != null) {
+			builder.append(" messageId=").append(messageId);
+		}
+		if (canonicalRegistrationId != null) {
+			builder.append(" canonicalRegistrationId=").append(
+					canonicalRegistrationId);
+		}
+		if (errorCode != null) {
+			builder.append(" errorCode=").append(errorCode);
+		}
+		if (success != null) {
+			builder.append(" groupSuccess=").append(success);
+		}
+		if (failure != null) {
+			builder.append(" groupFailure=").append(failure);
+		}
+		if (failedRegistrationIds != null) {
+			builder.append(" failedRegistrationIds=").append(
+					failedRegistrationIds);
+		}
+		return builder.append(" ]").toString();
+	}
+
+	public int getStatus() {
+		return this.status;
+	}
+	
+	
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java
new file mode 100644
index 0000000..cc9970e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/Sender.java
@@ -0,0 +1,832 @@
+/**
+ * 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.gcm.domain;
+
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_CANONICAL_IDS;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_ERROR;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_FAILURE;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_MESSAGE_ID;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_MULTICAST_ID;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_BADGE;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_BODY;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_BODY_LOC_ARGS;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_BODY_LOC_KEY;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_CLICK_ACTION;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_COLOR;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_ICON;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_SOUND;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_TAG;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_TITLE;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_TITLE_LOC_ARGS;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_NOTIFICATION_TITLE_LOC_KEY;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_PAYLOAD;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_REGISTRATION_IDS;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_TO;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_RESULTS;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.JSON_SUCCESS;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.PARAM_COLLAPSE_KEY;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.PARAM_DELAY_WHILE_IDLE;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.PARAM_DRY_RUN;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.PARAM_PRIORITY;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.PARAM_CONTENT_AVAILABLE;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.PARAM_RESTRICTED_PACKAGE_NAME;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.PARAM_TIME_TO_LIVE;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.TOKEN_CANONICAL_REG_ID;
+import static org.apache.fineract.infrastructure.gcm.GcmConstants.TOPIC_PREFIX;
+
+import org.apache.fineract.infrastructure.gcm.GcmConstants;
+import org.apache.fineract.infrastructure.gcm.exception.InvalidRequestException;
+/*import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;*/
+
+
+
+
+
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Helper class to send messages to the GCM service using an API Key.
+ */
+public class Sender {
+
+	protected static final String UTF8 = "UTF-8";
+
+	/**
+	 * Initial delay before first retry, without jitter.
+	 */
+	protected static final int BACKOFF_INITIAL_DELAY = 1000;
+	/**
+	 * Maximum delay before a retry.
+	 */
+	protected static final int MAX_BACKOFF_DELAY = 1024000;
+
+	protected final Random random = new Random();
+	protected static final Logger logger = Logger.getLogger(Sender.class
+			.getName());
+
+	private final String key;
+
+	private String endpoint;
+
+	private int connectTimeout;
+	private int readTimeout;
+	
+	/**
+	 * Full options constructor.
+	 *
+	 * @param key
+	 *            FCM Server Key obtained through the Firebase Web Console.
+	 * @param endpoint
+	 *            Endpoint to use when sending the message.
+	 */
+	public Sender(String key, String endpoint) {
+		this.key = nonNull(key);
+		this.endpoint = nonNull(endpoint);
+	}
+
+	public String getEndpoint() {
+		return endpoint;
+	}
+
+	/**
+	 * Set the underlying URLConnection's connect timeout (in milliseconds). A
+	 * timeout value of 0 specifies an infinite timeout.
+	 * <p>
+	 * Default is the system's default timeout.
+	 *
+	 * @see java.net.URLConnection#setConnectTimeout(int)
+	 */
+	public final void setConnectTimeout(int connectTimeout) {
+		if (connectTimeout < 0) {
+			throw new IllegalArgumentException("timeout can not be negative");
+		}
+		this.connectTimeout = connectTimeout;
+	}
+
+	/**
+	 * Set the underlying URLConnection's read timeout (in milliseconds). A
+	 * timeout value of 0 specifies an infinite timeout.
+	 * <p>
+	 * Default is the system's default timeout.
+	 *
+	 * @see java.net.URLConnection#setReadTimeout(int)
+	 */
+	public final void setReadTimeout(int readTimeout) {
+		if (readTimeout < 0) {
+			throw new IllegalArgumentException("timeout can not be negative");
+		}
+		this.readTimeout = readTimeout;
+	}
+
+	/**
+	 * Sends a message to one device, retrying in case of unavailability.
+	 *
+	 * <p>
+	 * <strong>Note: </strong> this method uses exponential back-off to retry in
+	 * case of service unavailability and hence could block the calling thread
+	 * for many seconds.
+	 *
+	 * @param message
+	 *            message to be sent, including the device's registration id.
+	 * @param to
+	 *            registration token, notification key, or topic where the
+	 *            message will be sent.
+	 * @param retries
+	 *            number of retries in case of service unavailability errors.
+	 *
+	 * @return result of the request (see its javadoc for more details).
+	 *
+	 * @throws IllegalArgumentException
+	 *             if to is {@literal null}.
+	 * @throws InvalidRequestException
+	 *             if GCM didn't returned a 200 or 5xx status.
+	 * @throws IOException
+	 *             if message could not be sent.
+	 */
+	public Result send(Message message, String to, int retries)
+			throws IOException {
+		int attempt = 0;
+		Result result;
+		int backoff = BACKOFF_INITIAL_DELAY;
+		boolean tryAgain;
+		do {
+			attempt++;
+			if (logger.isLoggable(Level.FINE)) {
+				logger.fine("Attempt #" + attempt + " to send message "
+						+ message + " to regIds " + to);
+			}
+			result = sendNoRetry(message, to);
+			tryAgain = result == null && attempt <= retries;
+			if (tryAgain) {
+				int sleepTime = backoff / 2 + random.nextInt(backoff);
+				sleep(sleepTime);
+				if (2 * backoff < MAX_BACKOFF_DELAY) {
+					backoff *= 2;
+				}
+			}
+		} while (tryAgain);
+		if (result == null) {
+			throw new IOException("Could not send message after " + attempt
+					+ " attempts");
+		}
+		return result;
+	}
+
+	/**
+	 * Sends a message without retrying in case of service unavailability. See
+	 * {@link #send(Message, String, int)} for more info.
+	 *
+	 * @return result of the post, or {@literal null} if the GCM service was
+	 *         unavailable or any network exception caused the request to fail,
+	 *         or if the response contains more than one result.
+	 *
+	 * @throws InvalidRequestException
+	 *             if GCM didn't returned a 200 status.
+	 * @throws IllegalArgumentException
+	 *             if to is {@literal null}.
+	 */
+	public Result sendNoRetry(Message message, String to) throws IOException {
+		nonNull(to);
+		Map<Object, Object> jsonRequest = new HashMap<>();
+		messageToMap(message, jsonRequest);
+		jsonRequest.put(JSON_TO, to);
+		Map<String , Object> responseMap = makeGcmHttpRequest(jsonRequest);
+		String responseBody = null;
+		if (responseMap.get("responseBody") != null) {
+			responseBody = (String) responseMap.get("responseBody");
+		}
+		int status = (int) responseMap.get("status");
+		//responseBody
+		if (responseBody == null) {
+			return null;
+		}
+		JsonParser jsonParser = new JsonParser();
+		JsonObject jsonResponse;
+		try {
+			jsonResponse = (JsonObject) jsonParser.parse(responseBody);
+			Result.Builder resultBuilder = new Result.Builder();
+			if (jsonResponse.has("results")) {
+				// Handle response from message sent to specific device.
+				JsonArray jsonResults = (JsonArray) jsonResponse.get("results");
+				if (jsonResults.size() == 1) {
+					JsonObject jsonResult = (JsonObject) jsonResults.get(0);
+					String messageId = null;
+					String canonicalRegId = null;
+					String error = null;
+					if(jsonResult.has(JSON_MESSAGE_ID)){
+						messageId = jsonResult.get(JSON_MESSAGE_ID).getAsString();
+					}
+					if(jsonResult.has(TOKEN_CANONICAL_REG_ID)){
+						canonicalRegId = jsonResult
+								.get(TOKEN_CANONICAL_REG_ID).getAsString();
+					}
+					if(jsonResult.has(JSON_ERROR)){
+						error = (String) jsonResult.get(JSON_ERROR).getAsString();
+					}
+					int success = 0;
+					int failure = 0;
+					if(jsonResponse.get("success") != null){
+						success = Integer.parseInt(jsonResponse.get("success").toString());
+					}
+					if(jsonResponse.get("failure") != null){
+						failure = Integer.parseInt(jsonResponse.get("failure").toString());
+					}
+					resultBuilder.messageId(messageId)
+							.canonicalRegistrationId(canonicalRegId)
+							.success(success)
+							.failure(failure)
+							.status(status)
+							.errorCode(error);
+				} else {
+					logger.log(Level.WARNING,
+							"Found null or " + jsonResults.size()
+									+ " results, expected one");
+					return null;
+				}
+			} else if (to.startsWith(TOPIC_PREFIX)) {
+				if (jsonResponse.has(JSON_MESSAGE_ID)) {
+					// message_id is expected when this is the response from a
+					// topic message.
+					Long messageId = jsonResponse.get(JSON_MESSAGE_ID).getAsLong();
+					resultBuilder.messageId(messageId.toString());
+				} else if (jsonResponse.has(JSON_ERROR)) {
+					String error = jsonResponse.get(JSON_ERROR).getAsString();
+					resultBuilder.errorCode(error);
+				} else {
+					logger.log(Level.WARNING, "Expected " + JSON_MESSAGE_ID
+							+ " or " + JSON_ERROR + " found: " + responseBody);
+					return null;
+				}
+			} else if (jsonResponse.has(JSON_SUCCESS)
+					&& jsonResponse.has(JSON_FAILURE)) {
+				// success and failure are expected when response is from group
+				// message.
+				int success = getNumber(responseMap, JSON_SUCCESS).intValue();
+				int failure = getNumber(responseMap, JSON_FAILURE).intValue();
+				List<String> failedIds = null;
+				if (jsonResponse.has("failed_registration_ids")) {
+					JsonArray jFailedIds = (JsonArray) jsonResponse
+							.get("failed_registration_ids").getAsJsonArray();
+					failedIds = new ArrayList<>();
+					for (int i = 0; i < jFailedIds.size(); i++) {
+						failedIds.add(jFailedIds.get(i).getAsString());
+					}
+				}
+				resultBuilder.success(success).failure(failure)
+						.failedRegistrationIds(failedIds);
+			} else {
+				logger.warning("Unrecognized response: " + responseBody);
+				throw newIoException(responseBody, new Exception(
+						"Unrecognized response."));
+			}
+			return resultBuilder.build();
+		} catch (CustomParserException e) {
+			throw newIoException(responseBody, e);
+		}
+	}
+
+	/**
+	 * Sends a message to many devices, retrying in case of unavailability.
+	 *
+	 * <p>
+	 * <strong>Note: </strong> this method uses exponential back-off to retry in
+	 * case of service unavailability and hence could block the calling thread
+	 * for many seconds.
+	 *
+	 * @param message
+	 *            message to be sent.
+	 * @param regIds
+	 *            registration id of the devices that will receive the message.
+	 * @param retries
+	 *            number of retries in case of service unavailability errors.
+	 *
+	 * @return combined result of all requests made.
+	 *
+	 * @throws IllegalArgumentException
+	 *             if registrationIds is {@literal null} or empty.
+	 * @throws InvalidRequestException
+	 *             if GCM didn't returned a 200 or 503 status.
+	 * @throws IOException
+	 *             if message could not be sent.
+	 */
+	public MulticastResult send(Message message, List<String> regIds,
+			int retries) throws IOException {
+		int attempt = 0;
+		MulticastResult multicastResult;
+		int backoff = BACKOFF_INITIAL_DELAY;
+		// Map of results by registration id, it will be updated after each
+		// attempt
+		// to send the messages
+		Map<String, Result> results = new HashMap<>();
+		List<String> unsentRegIds = new ArrayList<>(regIds);
+		boolean tryAgain;
+		List<Long> multicastIds = new ArrayList<>();
+		do {
+			multicastResult = null;
+			attempt++;
+			if (logger.isLoggable(Level.FINE)) {
+				logger.fine("Attempt #" + attempt + " to send message "
+						+ message + " to regIds " + unsentRegIds);
+			}
+			try {
+				multicastResult = sendNoRetry(message, unsentRegIds);
+			} catch (IOException e) {
+				// no need for WARNING since exception might be already logged
+				logger.log(Level.FINEST, "IOException on attempt " + attempt, e);
+			}
+			if (multicastResult != null) {
+				long multicastId = multicastResult.getMulticastId();
+				logger.fine("multicast_id on attempt # " + attempt + ": "
+						+ multicastId);
+				multicastIds.add(multicastId);
+				unsentRegIds = updateStatus(unsentRegIds, results,
+						multicastResult);
+				tryAgain = !unsentRegIds.isEmpty() && attempt <= retries;
+			} else {
+				tryAgain = attempt <= retries;
+			}
+			if (tryAgain) {
+				int sleepTime = backoff / 2 + random.nextInt(backoff);
+				sleep(sleepTime);
+				if (2 * backoff < MAX_BACKOFF_DELAY) {
+					backoff *= 2;
+				}
+			}
+		} while (tryAgain);
+		if (multicastIds.isEmpty()) {
+			// all JSON posts failed due to GCM unavailability
+			throw new IOException("Could not post JSON requests to GCM after "
+					+ attempt + " attempts");
+		}
+		// calculate summary
+		int success = 0, failure = 0, canonicalIds = 0;
+		for (Result result : results.values()) {
+			if (result.getMessageId() != null) {
+				success++;
+				if (result.getCanonicalRegistrationId() != null) {
+					canonicalIds++;
+				}
+			} else {
+				failure++;
+			}
+		}
+		// build a new object with the overall result
+		long multicastId = multicastIds.remove(0);
+		MulticastResult.Builder builder = new MulticastResult.Builder(success,
+				failure, canonicalIds, multicastId)
+				.retryMulticastIds(multicastIds);
+		// add results, in the same order as the input
+		for (String regId : regIds) {
+			Result result = results.get(regId);
+			builder.addResult(result);
+		}
+		return builder.build();
+	}
+
+	/**
+	 * Updates the status of the messages sent to devices and the list of
+	 * devices that should be retried.
+	 *
+	 * @param unsentRegIds
+	 *            list of devices that are still pending an update.
+	 * @param allResults
+	 *            map of status that will be updated.
+	 * @param multicastResult
+	 *            result of the last multicast sent.
+	 *
+	 * @return updated version of devices that should be retried.
+	 */
+	private List<String> updateStatus(List<String> unsentRegIds,
+			Map<String, Result> allResults, MulticastResult multicastResult) {
+		List<Result> results = multicastResult.getResults();
+		if (results.size() != unsentRegIds.size()) {
+			// should never happen, unless there is a flaw in the algorithm
+			throw new RuntimeException("Internal error: sizes do not match. "
+					+ "currentResults: " + results + "; unsentRegIds: "
+					+ unsentRegIds);
+		}
+		List<String> newUnsentRegIds = new ArrayList<>();
+		for (int i = 0; i < unsentRegIds.size(); i++) {
+			String regId = unsentRegIds.get(i);
+			Result result = results.get(i);
+			allResults.put(regId, result);
+			String error = result.getErrorCodeName();
+			if (error != null
+					&& (error.equals(GcmConstants.ERROR_UNAVAILABLE) || error
+							.equals(GcmConstants.ERROR_INTERNAL_SERVER_ERROR))) {
+				newUnsentRegIds.add(regId);
+			}
+		}
+		return newUnsentRegIds;
+	}
+
+	/**
+	 * Sends a message without retrying in case of service unavailability. See
+	 * {@link #send(Message, List, int)} for more info.
+	 *
+	 * @return multicast results if the message was sent successfully,
+	 *         {@literal null} if it failed but could be retried.
+	 *
+	 * @throws IllegalArgumentException
+	 *             if registrationIds is {@literal null} or empty.
+	 * @throws InvalidRequestException
+	 *             if GCM didn't returned a 200 status.
+	 * @throws IOException
+	 *             if there was a JSON parsing error
+	 */
+	public MulticastResult sendNoRetry(Message message,
+			List<String> registrationIds) throws IOException {
+		if (nonNull(registrationIds).isEmpty()) {
+			throw new IllegalArgumentException(
+					"registrationIds cannot be empty");
+		}
+		Map<Object, Object> jsonRequest = new HashMap<>();
+		messageToMap(message, jsonRequest);
+		jsonRequest.put(JSON_REGISTRATION_IDS, registrationIds);
+		Map<String , Object> responseMap = makeGcmHttpRequest(jsonRequest);
+		String responseBody = null;
+		if (responseMap.get("responseBody") != null) {
+			responseBody = (String) responseMap.get("responseBody");
+		}
+		if (responseBody == null) {
+			return null;
+		}
+		
+		JsonParser parser = new JsonParser();
+		JsonObject jsonResponse;
+		try {
+			jsonResponse = (JsonObject) parser.parse(responseBody);
+			int success = getNumber(responseMap, JSON_SUCCESS).intValue();
+			int failure = getNumber(responseMap, JSON_FAILURE).intValue();
+			int canonicalIds = getNumber(responseMap, JSON_CANONICAL_IDS)
+					.intValue();
+			long multicastId = getNumber(responseMap, JSON_MULTICAST_ID)
+					.longValue();
+			MulticastResult.Builder builder = new MulticastResult.Builder(
+					success, failure, canonicalIds, multicastId);
+			@SuppressWarnings("unchecked")
+			List<Map<String, Object>> results = (List<Map<String, Object>>) jsonResponse
+					.get(JSON_RESULTS);
+			if (results != null) {
+				for (Map<String, Object> jsonResult : results) {
+					String messageId = (String) jsonResult.get(JSON_MESSAGE_ID);
+					String canonicalRegId = (String) jsonResult
+							.get(TOKEN_CANONICAL_REG_ID);
+					String error = (String) jsonResult.get(JSON_ERROR);
+					Result result = new Result.Builder().messageId(messageId)
+							.canonicalRegistrationId(canonicalRegId)
+							.errorCode(error).build();
+					builder.addResult(result);
+				}
+			}
+			return builder.build();
+		} catch (CustomParserException e) {
+			throw newIoException(responseBody, e);
+		}
+	}
+
+	private Map<String , Object> makeGcmHttpRequest(Map<Object, Object> jsonRequest)
+			throws InvalidRequestException {
+		String requestBody = new Gson().toJson(jsonRequest);
+		logger.finest("JSON request: " + requestBody);
+		HttpURLConnection conn;
+		int status;
+		try {
+			conn = post(getEndpoint(), "application/json", requestBody);
+			status = conn.getResponseCode();
+		} catch (IOException e) {
+			logger.log(Level.FINE, "IOException posting to GCM", e);
+			return null;
+		}
+		String responseBody;
+		if (status != 200) {
+			try {
+				responseBody = getAndClose(conn.getErrorStream());
+				logger.finest("JSON error response: " + responseBody);
+			} catch (IOException e) {
+				// ignore the exception since it will thrown an
+				// InvalidRequestException
+				// anyways
+				responseBody = "N/A";
+				logger.log(Level.FINE, "Exception reading response: ", e);
+			}
+			throw new InvalidRequestException(status, responseBody);
+		}
+		try {
+			responseBody = getAndClose(conn.getInputStream());
+		} catch (IOException e) {
+			logger.log(Level.WARNING, "IOException reading response", e);
+			return null;
+		}
+		logger.finest("JSON response: " + responseBody);
+		Map<String , Object> map = new HashMap<>();
+		map.put("responseBody", responseBody);
+		map.put("status", status);
+		
+		return map;
+	}
+
+	/**
+	 * Populate Map with message.
+	 *
+	 * @param message
+	 *            Message used to populate Map.
+	 * @param mapRequest
+	 *            Map populated by Message.
+	 */
+	private void messageToMap(Message message, Map<Object, Object> mapRequest) {
+		if (message == null || mapRequest == null) {
+			return;
+		}
+		setJsonField(mapRequest, PARAM_PRIORITY, message.getPriority());
+		setJsonField(mapRequest, PARAM_CONTENT_AVAILABLE,
+				message.getContentAvailable());
+		setJsonField(mapRequest, PARAM_TIME_TO_LIVE, message.getTimeToLive());
+		setJsonField(mapRequest, PARAM_COLLAPSE_KEY, message.getCollapseKey());
+		setJsonField(mapRequest, PARAM_RESTRICTED_PACKAGE_NAME,
+				message.getRestrictedPackageName());
+		setJsonField(mapRequest, PARAM_DELAY_WHILE_IDLE,
+				message.isDelayWhileIdle());
+		setJsonField(mapRequest, PARAM_DRY_RUN, message.isDryRun());
+		Map<String, String> payload = message.getData();
+		if (!payload.isEmpty()) {
+			mapRequest.put(JSON_PAYLOAD, payload);
+		}
+		if (message.getNotification() != null) {
+			Notification notification = message.getNotification();
+			Map<Object, Object> nMap = new HashMap<>();
+			if (notification.getBadge() != null) {
+				setJsonField(nMap, JSON_NOTIFICATION_BADGE, notification
+						.getBadge().toString());
+			}
+			setJsonField(nMap, JSON_NOTIFICATION_BODY, notification.getBody());
+			setJsonField(nMap, JSON_NOTIFICATION_BODY_LOC_ARGS,
+					notification.getBodyLocArgs());
+			setJsonField(nMap, JSON_NOTIFICATION_BODY_LOC_KEY,
+					notification.getBodyLocKey());
+			setJsonField(nMap, JSON_NOTIFICATION_CLICK_ACTION,
+					notification.getClickAction());
+			setJsonField(nMap, JSON_NOTIFICATION_COLOR, notification.getColor());
+			setJsonField(nMap, JSON_NOTIFICATION_ICON, notification.getIcon());
+			setJsonField(nMap, JSON_NOTIFICATION_SOUND, notification.getSound());
+			setJsonField(nMap, JSON_NOTIFICATION_TAG, notification.getTag());
+			setJsonField(nMap, JSON_NOTIFICATION_TITLE, notification.getTitle());
+			setJsonField(nMap, JSON_NOTIFICATION_TITLE_LOC_ARGS,
+					notification.getTitleLocArgs());
+			setJsonField(nMap, JSON_NOTIFICATION_TITLE_LOC_KEY,
+					notification.getTitleLocKey());
+			mapRequest.put(JSON_NOTIFICATION, nMap);
+		}
+	}
+
+	private IOException newIoException(String responseBody, Exception e) {
+		// log exception, as IOException constructor that takes a message and
+		// cause
+		// is only available on Java 6
+		String msg = "Error parsing JSON response (" + responseBody + ")";
+		logger.log(Level.WARNING, msg, e);
+		return new IOException(msg + ":" + e);
+	}
+
+	private static void close(Closeable closeable) {
+		if (closeable != null) {
+			try {
+				closeable.close();
+			} catch (IOException e) {
+				// ignore error
+				logger.log(Level.FINEST, "IOException closing stream", e);
+			}
+		}
+	}
+
+	/**
+	 * Sets a JSON field, but only if the value is not {@literal null}.
+	 */
+	private void setJsonField(Map<Object, Object> json, String field,
+			Object value) {
+		if (value != null) {
+			json.put(field, value);
+		}
+	}
+
+	private Number getNumber(Map<?, ?> json, String field) {
+		Object value = json.get(field);
+		if (value == null) {
+			throw new CustomParserException("Missing field: " + field);
+		}
+		if (!(value instanceof Number)) {
+			throw new CustomParserException("Field " + field
+					+ " does not contain a number: " + value);
+		}
+		return (Number) value;
+	}
+
+	class CustomParserException extends RuntimeException {
+		CustomParserException(String message) {
+			super(message);
+		}
+	}
+
+	/**
+	 * Make an HTTP post to a given URL.
+	 *
+	 * @return HTTP response.
+	 */
+	protected HttpURLConnection post(String url, String body)
+			throws IOException {
+		return post(url, "application/x-www-form-urlencoded;charset=UTF-8",
+				body);
+	}
+
+	/**
+	 * Makes an HTTP POST request to a given endpoint.
+	 *
+	 * <p>
+	 * <strong>Note: </strong> the returned connected should not be
+	 * disconnected, otherwise it would kill persistent connections made using
+	 * Keep-Alive.
+	 *
+	 * @param url
+	 *            endpoint to post the request.
+	 * @param contentType
+	 *            type of request.
+	 * @param body
+	 *            body of the request.
+	 *
+	 * @return the underlying connection.
+	 *
+	 * @throws IOException
+	 *             propagated from underlying methods.
+	 */
+	protected HttpURLConnection post(String url, String contentType, String body)
+			throws IOException {
+		if (url == null || contentType == null || body == null) {
+			throw new IllegalArgumentException("arguments cannot be null");
+		}
+		if (!url.startsWith("https://")) {
+			logger.warning("URL does not use https: " + url);
+		}
+		logger.fine("Sending POST to " + url);
+		logger.finest("POST body: " + body);
+		byte[] bytes = body.getBytes(UTF8);
+		HttpURLConnection conn = getConnection(url);
+		conn.setDoOutput(true);
+		conn.setUseCaches(false);
+		conn.setFixedLengthStreamingMode(bytes.length);
+		conn.setRequestMethod("POST");
+		conn.setRequestProperty("Content-Type", contentType);
+		conn.setRequestProperty("Authorization", "key=" + key);
+		OutputStream out = conn.getOutputStream();
+		try {
+			out.write(bytes);
+		} finally {
+			close(out);
+		}
+		return conn;
+	}
+
+	/**
+	 * Creates a map with just one key-value pair.
+	 */
+	protected static final Map<String, String> newKeyValues(String key,
+			String value) {
+		Map<String, String> keyValues = new HashMap<>(1);
+		keyValues.put(nonNull(key), nonNull(value));
+		return keyValues;
+	}
+
+	/**
+	 * Creates a {@link StringBuilder} to be used as the body of an HTTP POST.
+	 *
+	 * @param name
+	 *            initial parameter for the POST.
+	 * @param value
+	 *            initial value for that parameter.
+	 * @return StringBuilder to be used an HTTP POST body.
+	 */
+	protected static StringBuilder newBody(String name, String value) {
+		return new StringBuilder(nonNull(name)).append('=').append(
+				nonNull(value));
+	}
+
+	/**
+	 * Adds a new parameter to the HTTP POST body.
+	 *
+	 * @param body
+	 *            HTTP POST body.
+	 * @param name
+	 *            parameter's name.
+	 * @param value
+	 *            parameter's value.
+	 */
+	protected static void addParameter(StringBuilder body, String name,
+			String value) {
+		nonNull(body).append('&').append(nonNull(name)).append('=')
+				.append(nonNull(value));
+	}
+
+	/**
+	 * Gets an {@link HttpURLConnection} given an URL.
+	 */
+	protected HttpURLConnection getConnection(String url) throws IOException {
+		HttpURLConnection conn = (HttpURLConnection) new URL(url)
+				.openConnection();
+		conn.setConnectTimeout(connectTimeout);
+		conn.setReadTimeout(readTimeout);
+		return conn;
+	}
+
+	/**
+	 * Convenience method to convert an InputStream to a String.
+	 * <p>
+	 * If the stream ends in a newline character, it will be stripped.
+	 * <p>
+	 * If the stream is {@literal null}, returns an empty string.
+	 */
+	protected static String getString(InputStream stream) throws IOException {
+		if (stream == null) {
+			return "";
+		}
+		BufferedReader reader = new BufferedReader(
+				new InputStreamReader(stream));
+		StringBuilder content = new StringBuilder();
+		String newLine;
+		do {
+			newLine = reader.readLine();
+			if (newLine != null) {
+				content.append(newLine).append('\n');
+			}
+		} while (newLine != null);
+		if (content.length() > 0) {
+			// strip last newline
+			content.setLength(content.length() - 1);
+		}
+		return content.toString();
+	}
+
+	private static String getAndClose(InputStream stream) throws IOException {
+		try {
+			return getString(stream);
+		} finally {
+			if (stream != null) {
+				close(stream);
+			}
+		}
+	}
+
+	static <T> T nonNull(T argument) {
+		if (argument == null) {
+			throw new IllegalArgumentException("argument cannot be null");
+		}
+		return argument;
+	}
+
+	void sleep(long millis) {
+		try {
+			Thread.sleep(millis);
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/DeviceRegistrationNotFoundException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/DeviceRegistrationNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/DeviceRegistrationNotFoundException.java
new file mode 100644
index 0000000..cc730a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/DeviceRegistrationNotFoundException.java
@@ -0,0 +1,38 @@
+/**
+ * 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.gcm.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class DeviceRegistrationNotFoundException extends
+		AbstractPlatformResourceNotFoundException {
+
+	public DeviceRegistrationNotFoundException(final Long id) {
+		super("error.msg.device.registration.id.invalid",
+				"Device registration with identifier " + id + " does not exist",
+				id);
+	}
+
+	public DeviceRegistrationNotFoundException(final Long clientId, String value) {
+		super("error.msg.device.registration." + value + ".invalid",
+				"Device registration with " + value + " identifier " + clientId
+						+ " does not exist", clientId);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/InvalidRequestException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/InvalidRequestException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/InvalidRequestException.java
new file mode 100644
index 0000000..2194b43
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/exception/InvalidRequestException.java
@@ -0,0 +1,66 @@
+/**
+ * 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.gcm.exception;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown when GCM returned an error due to an invalid request.
+ * <p>
+ * This is equivalent to GCM posts that return an HTTP error different of 200.
+ */
+public final class InvalidRequestException extends IOException {
+
+	private final int status;
+	private final String description;
+
+	public InvalidRequestException(int status) {
+		this(status, null);
+	}
+
+	public InvalidRequestException(int status, String description) {
+		super(getMessage(status, description));
+		this.status = status;
+		this.description = description;
+	}
+
+	private static String getMessage(int status, String description) {
+		StringBuilder base = new StringBuilder("HTTP Status Code: ")
+				.append(status);
+		if (description != null) {
+			base.append("(").append(description).append(")");
+		}
+		return base.toString();
+	}
+
+	/**
+	 * Gets the HTTP Status Code.
+	 */
+	public int getHttpStatusCode() {
+		return status;
+	}
+
+	/**
+	 * Gets the error description.
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformService.java
new file mode 100644
index 0000000..1b4ec42
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformService.java
@@ -0,0 +1,32 @@
+/**
+ * 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.gcm.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationData;
+
+public interface DeviceRegistrationReadPlatformService {
+
+	Collection<DeviceRegistrationData> retrieveAllDeviceRegiistrations();
+
+	DeviceRegistrationData retrieveDeviceRegiistration(Long id);
+
+	DeviceRegistrationData retrieveDeviceRegiistrationByClientId(Long clientId);
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.java
new file mode 100644
index 0000000..8c64fe3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationReadPlatformServiceImpl.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.gcm.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationData;
+import org.apache.fineract.infrastructure.gcm.exception.DeviceRegistrationNotFoundException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DeviceRegistrationReadPlatformServiceImpl implements
+		DeviceRegistrationReadPlatformService {
+
+	private final JdbcTemplate jdbcTemplate;
+	private final PlatformSecurityContext context;
+
+	@Autowired
+	public DeviceRegistrationReadPlatformServiceImpl(
+			final PlatformSecurityContext context,
+			final RoutingDataSource dataSource) {
+		this.context = context;
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+	}
+
+	private static final class DeviceRegistrationDataMapper implements
+			RowMapper<DeviceRegistrationData> {
+
+		private final String schema;
+
+		public DeviceRegistrationDataMapper() {
+			final StringBuilder sqlBuilder = new StringBuilder(200);
+			sqlBuilder
+					.append(" cdr.id as id, cdr.registration_id as registrationId, cdr.updatedon_date as updatedOnDate, ");
+			sqlBuilder
+					.append(" c.id as clientId, c.display_name as clientName ");
+			sqlBuilder.append(" from client_device_registration cdr ");
+			sqlBuilder.append(" left join m_client c on c.id = cdr.client_id ");
+			this.schema = sqlBuilder.toString();
+		}
+
+		public String schema() {
+			return this.schema;
+		}
+
+		@Override
+		public DeviceRegistrationData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+
+			final Long id = JdbcSupport.getLong(rs, "id");
+			final LocalDate updatedOnDate = JdbcSupport.getLocalDate(rs,
+					"updatedOnDate");
+			final String registrationId = rs.getString("registrationId");
+			final Long clientId = rs.getLong("clientId");
+			final String clientName = rs.getString("clientName");
+			ClientData clientData = ClientData.instance(clientId, clientName);
+			return DeviceRegistrationData.instance(id, clientData,
+					registrationId, updatedOnDate.toDate());
+		}
+	}
+
+	@Override
+	public Collection<DeviceRegistrationData> retrieveAllDeviceRegiistrations() {
+		this.context.authenticatedUser();
+		DeviceRegistrationDataMapper drm = new DeviceRegistrationDataMapper();
+		String sql = "select " + drm.schema();
+		return this.jdbcTemplate.query(sql, drm, new Object[] {});
+	}
+
+	@Override
+	public DeviceRegistrationData retrieveDeviceRegiistration(Long id) {
+		try {
+			this.context.authenticatedUser();
+			DeviceRegistrationDataMapper drm = new DeviceRegistrationDataMapper();
+			String sql = "select " + drm.schema() + " where cdr.id = ? ";
+			return this.jdbcTemplate.queryForObject(sql, drm,
+					new Object[] { id });
+		} catch (final EmptyResultDataAccessException e) {
+			throw new DeviceRegistrationNotFoundException(id);
+		}
+	}
+
+	@Override
+	public DeviceRegistrationData retrieveDeviceRegiistrationByClientId(
+			Long clientId) {
+		try {
+			this.context.authenticatedUser();
+			DeviceRegistrationDataMapper drm = new DeviceRegistrationDataMapper();
+			String sql = "select " + drm.schema() + " where c.id = ? ";
+			return this.jdbcTemplate.queryForObject(sql, drm,
+					new Object[] { clientId });
+		} catch (final EmptyResultDataAccessException e) {
+			throw new DeviceRegistrationNotFoundException(clientId, "client");
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformService.java
new file mode 100644
index 0000000..2391fdd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformService.java
@@ -0,0 +1,30 @@
+/**
+ * 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.gcm.service;
+
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistration;
+
+public interface DeviceRegistrationWritePlatformService {
+
+    public DeviceRegistration registerDevice(final Long clientId, final String registrationId);
+
+    public DeviceRegistration updateDeviceRegistration(final Long id, final Long clientId, final String registrationId);
+
+    public void deleteDeviceRegistration(final Long id);
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformServiceImpl.java
new file mode 100644
index 0000000..52653ea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/DeviceRegistrationWritePlatformServiceImpl.java
@@ -0,0 +1,123 @@
+/**
+ * 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.gcm.service;
+
+import javax.persistence.PersistenceException;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistration;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationRepositoryWrapper;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.openjpa.persistence.EntityExistsException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class DeviceRegistrationWritePlatformServiceImpl implements
+		DeviceRegistrationWritePlatformService {
+
+	private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository;
+	private final ClientRepositoryWrapper clientRepositoryWrapper;
+	private final PlatformSecurityContext context;
+
+	@Autowired
+	public DeviceRegistrationWritePlatformServiceImpl(
+			final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository,
+			final ClientRepositoryWrapper clientRepositoryWrapper,
+			final PlatformSecurityContext context) {
+		this.deviceRegistrationRepository = deviceRegistrationRepository;
+		this.clientRepositoryWrapper = clientRepositoryWrapper;
+		this.context = context;
+	}
+
+	@Transactional
+	@Override
+	public DeviceRegistration registerDevice(Long clientId,
+			String registrationId) {
+		this.context.authenticatedUser();
+		Client client = this.clientRepositoryWrapper
+				.findOneWithNotFoundDetection(clientId);
+		try {
+			DeviceRegistration deviceRegistration = DeviceRegistration
+					.instance(client, registrationId);
+			this.deviceRegistrationRepository.save(deviceRegistration);
+			return deviceRegistration;
+		} catch (final EntityExistsException dve) {
+			handleDataIntegrityIssues(registrationId, dve, dve);
+			return null;
+		} catch (final DataIntegrityViolationException dve) {
+			handleDataIntegrityIssues(registrationId,
+					dve.getMostSpecificCause(), dve);
+			return null;
+		} catch (final PersistenceException dve) {
+			Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
+			handleDataIntegrityIssues(registrationId, throwable, dve);
+			return null;
+		} catch (final Exception dve) {
+			Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
+			handleDataIntegrityIssues(registrationId, throwable, dve);
+			return null;
+		}
+
+	}
+
+	private void handleDataIntegrityIssues(final String registrationId,
+			final Throwable realCause,
+			@SuppressWarnings("unused") final Exception dve) {
+
+		if (realCause.getMessage().contains("registration_key")) {
+			throw new PlatformDataIntegrityException(
+					"error.msg.duplicate.device.registration.id",
+					"Registration id : " + registrationId + " already exist.",
+					"name", registrationId);
+		}
+
+		throw new PlatformDataIntegrityException(
+				"error.msg.charge.unknown.data.integrity.issue",
+				"Unknown data integrity issue with resource: "
+						+ realCause.getMessage());
+	}
+
+	@Override
+	public DeviceRegistration updateDeviceRegistration(Long id, Long clientId,
+			String registrationId) {
+		DeviceRegistration deviceRegistration = this.deviceRegistrationRepository
+				.findOneWithNotFoundDetection(id);
+		Client client = this.clientRepositoryWrapper
+				.findOneWithNotFoundDetection(clientId);
+		deviceRegistration.setClient(client);
+		deviceRegistration.setRegistrationId(registrationId);
+		deviceRegistration.setUpdatedOnDate(DateUtils
+				.getLocalDateTimeOfTenant().toDate());
+		return deviceRegistration;
+	}
+
+    @Override
+    public void deleteDeviceRegistration(Long id) {
+        DeviceRegistration deviceRegistration = this.deviceRegistrationRepository.findOneWithNotFoundDetection(id);
+        this.deviceRegistrationRepository.delete(deviceRegistration);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java
new file mode 100644
index 0000000..67fb072
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/service/NotificationSenderService.java
@@ -0,0 +1,133 @@
+/**
+ * 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.gcm.service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.gcm.GcmConstants;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistration;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationRepositoryWrapper;
+import org.apache.fineract.infrastructure.gcm.domain.Message;
+import org.apache.fineract.infrastructure.gcm.domain.Message.Builder;
+import org.apache.fineract.infrastructure.gcm.domain.Message.Priority;
+import org.apache.fineract.infrastructure.gcm.domain.Notification;
+import org.apache.fineract.infrastructure.gcm.domain.NotificationConfigurationData;
+import org.apache.fineract.infrastructure.gcm.domain.Result;
+import org.apache.fineract.infrastructure.gcm.domain.Sender;
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class NotificationSenderService {
+
+	private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepositoryWrapper;
+	private final SmsMessageRepository smsMessageRepository;
+	private ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService;
+
+	@Autowired
+	public NotificationSenderService(
+			final DeviceRegistrationRepositoryWrapper deviceRegistrationRepositoryWrapper,
+			final SmsMessageRepository smsMessageRepository, final ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService) {
+		this.deviceRegistrationRepositoryWrapper = deviceRegistrationRepositoryWrapper;
+		this.smsMessageRepository = smsMessageRepository;
+		this.propertiesReadPlatformService = propertiesReadPlatformService;
+	}
+
+	public void sendNotification(List<SmsMessage> smsMessages) {
+		Map<Long, List<SmsMessage>> notificationByEachClient = getNotificationListByClient(smsMessages);
+		for (Map.Entry<Long, List<SmsMessage>> entry : notificationByEachClient
+				.entrySet()) {
+			this.sendNotifiaction(entry.getKey(), entry.getValue());
+		}
+	}
+
+	public Map<Long, List<SmsMessage>> getNotificationListByClient(
+			List<SmsMessage> smsMessages) {
+		Map<Long, List<SmsMessage>> notificationByEachClient = new HashMap<>();
+		for (SmsMessage smsMessage : smsMessages) {
+			if (smsMessage.getClient() != null) {
+				Long clientId = smsMessage.getClient().getId();
+				if (notificationByEachClient.containsKey(clientId)) {
+					notificationByEachClient.get(clientId).add(smsMessage);
+				} else {
+					List<SmsMessage> msgList = new ArrayList<>(
+							Arrays.asList(smsMessage));
+					notificationByEachClient.put(clientId, msgList);
+				}
+
+			}
+		}
+		return notificationByEachClient;
+	}
+
+	public void sendNotifiaction(Long clientId, List<SmsMessage> smsList) {
+
+		DeviceRegistration deviceRegistration = this.deviceRegistrationRepositoryWrapper
+				.findDeviceRegistrationByClientId(clientId);
+		NotificationConfigurationData notificationConfigurationData = this.propertiesReadPlatformService.getNotificationConfiguration();
+		String registrationId = null;
+		if (deviceRegistration != null) {
+			registrationId = deviceRegistration.getRegistrationId();
+		}
+		for (SmsMessage smsMessage : smsList) {
+			try {
+				Notification notification = new Notification.Builder(
+						GcmConstants.defaultIcon).title(GcmConstants.title)
+						.body(smsMessage.getMessage()).build();
+				Builder b = new Builder();
+				b.notification(notification);
+				b.dryRun(false);
+				b.contentAvailable(true);
+				b.timeToLive(GcmConstants.TIME_TO_LIVE);
+				b.priority(Priority.HIGH);
+				b.delayWhileIdle(true);
+				Message msg = b.build();
+				Sender s = new Sender(notificationConfigurationData.getServerKey(),notificationConfigurationData.getFcmEndPoint());
+				Result res;
+
+				res = s.send(msg, registrationId, 3);
+				if (res.getSuccess() != null && res.getSuccess()>0) {
+					smsMessage.setStatusType(SmsMessageStatusType.SENT
+							.getValue());
+					smsMessage.setDeliveredOnDate(DateUtils.getLocalDateOfTenant().toDate());
+				} else if (res.getFailure() != null && res.getFailure()>0) {
+					smsMessage.setStatusType(SmsMessageStatusType.FAILED
+							.getValue());
+				}
+			} catch (IOException e) {
+				smsMessage
+						.setStatusType(SmsMessageStatusType.FAILED.getValue());
+			}
+		}
+
+		this.smsMessageRepository.save(smsList);
+
+	}
+
+}


[3/4] fineract git commit: notification sms

Posted by na...@apache.org.
notification sms


Project: http://git-wip-us.apache.org/repos/asf/fineract/repo
Commit: http://git-wip-us.apache.org/repos/asf/fineract/commit/8f30c210
Tree: http://git-wip-us.apache.org/repos/asf/fineract/tree/8f30c210
Diff: http://git-wip-us.apache.org/repos/asf/fineract/diff/8f30c210

Branch: refs/heads/develop
Commit: 8f30c2102f9f3526ac274f48e11ab2a5f91a86ff
Parents: d6accae
Author: nazeer shaik <na...@confluxtechnologies.com>
Authored: Thu Nov 2 16:24:28 2017 +0530
Committer: nazeer shaik <na...@confluxtechnologies.com>
Committed: Thu Nov 2 16:24:28 2017 +0530

----------------------------------------------------------------------
 .../campaigns/constants/CampaignType.java       |  12 +-
 .../sms/constants/SmsCampaignEnumerations.java  |   3 +
 .../campaigns/sms/data/SmsCampaignData.java     |  16 +-
 .../campaigns/sms/domain/SmsCampaign.java       |  36 +-
 .../sms/domain/SmsCampaignRepository.java       |   2 +
 .../sms/serialization/SmsCampaignValidator.java |  38 +-
 .../service/SmsCampaignDomainServiceImpl.java   |  23 +-
 ...CampaignDropdownReadPlatformServiceImpl.java |   3 -
 .../SmsCampaignReadPlatformServiceImpl.java     |   7 +-
 .../SmsCampaignWritePlatformServiceJpaImpl.java |  49 +-
 ...ropertiesCommandFromApiJsonDeserializer.java |   6 +
 .../service/ExternalServicesConstants.java      |  36 +
 ...alServicesPropertiesReadPlatformService.java |   3 +
 ...rvicesPropertiesReadPlatformServiceImpl.java |  35 +
 ...ExternalServicesReadPlatformServiceImpl.java |   4 +
 .../infrastructure/gcm/GcmConstants.java        | 242 ++++++
 .../gcm/api/DeviceRegistrationApiConstants.java |  25 +
 .../gcm/api/DeviceRegistrationApiResource.java  | 160 ++++
 .../gcm/domain/DeviceRegistration.java          |  85 ++
 .../gcm/domain/DeviceRegistrationData.java      |  47 ++
 .../domain/DeviceRegistrationRepository.java    |  36 +
 .../DeviceRegistrationRepositoryWrapper.java    |  61 ++
 .../infrastructure/gcm/domain/Message.java      | 317 +++++++
 .../gcm/domain/MulticastResult.java             | 151 ++++
 .../infrastructure/gcm/domain/Notification.java | 330 ++++++++
 .../domain/NotificationConfigurationData.java   |  49 ++
 .../infrastructure/gcm/domain/Result.java       | 187 +++++
 .../infrastructure/gcm/domain/Sender.java       | 832 +++++++++++++++++++
 .../DeviceRegistrationNotFoundException.java    |  38 +
 .../gcm/exception/InvalidRequestException.java  |  66 ++
 .../DeviceRegistrationReadPlatformService.java  |  32 +
 ...viceRegistrationReadPlatformServiceImpl.java | 125 +++
 .../DeviceRegistrationWritePlatformService.java |  30 +
 ...iceRegistrationWritePlatformServiceImpl.java | 123 +++
 .../gcm/service/NotificationSenderService.java  | 133 +++
 .../infrastructure/sms/domain/SmsMessage.java   |  32 +-
 .../sms/domain/SmsMessageAssembler.java         |   4 +-
 .../SmsMessageScheduledJobServiceImpl.java      |  36 +-
 .../portfolio/client/data/ClientData.java       |   6 +
 ...iceRegistrationWritePlatformServiceImpl.java |   3 +-
 .../core_db/V336__sms_campaign_notification.sql |  48 ++
 41 files changed, 3409 insertions(+), 62 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java
----------------------------------------------------------------------
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 b6a0aaf..421882a 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());
+    }
 }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java
index 99de55e..6e74932 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java
@@ -61,6 +61,9 @@ public class SmsCampaignEnumerations {
             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;
     }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java
index 8a6e6a3..cbd9bd5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java
@@ -44,6 +44,8 @@ public class SmsCampaignData {
     private final DateTime recurrenceStartDate;
     private final String recurrence;
     private final Long providerId;
+    private final boolean isNotification;
+    
 
     private final Collection<SmsProviderData> smsProviderOptions;
 
@@ -70,7 +72,7 @@ public class SmsCampaignData {
             final Collection<SmsProviderData> smsProviderOptions, final Collection<EnumOptionData> campaignTypeOptions,
             final Collection<EnumOptionData> triggerTypeOptions, final Collection<EnumOptionData> months, 
             final Collection<EnumOptionData> weekDays, final Collection<EnumOptionData> frequencyTypeOptions, 
-            final Collection<EnumOptionData> periodFrequencyOptions) {
+            final Collection<EnumOptionData> periodFrequencyOptions, final boolean isNotification) {
         this.id = id;
         this.campaignName = campaignName;
         this.campaignType = campaignType;
@@ -90,6 +92,7 @@ public class SmsCampaignData {
         } else {
             this.lastTriggerDate = null;
         }
+        this.isNotification = isNotification;
         this.smsCampaignTimeLine = smsCampaignTimeLine;
         this.recurrenceStartDate = recurrenceStartDate;
         this.recurrence = recurrence;
@@ -109,7 +112,7 @@ public class SmsCampaignData {
             final Long runReportId, final String reportName, final String paramValue, final EnumOptionData campaignStatus,
             final String message, final DateTime nextTriggerDate, final LocalDate lastTriggerDate,
             final SmsCampaignTimeLine smsCampaignTimeLine, final DateTime recurrenceStartDate, final String recurrence,
-            final Long providerId) {
+            final Long providerId, final boolean isNotification) {
         final Collection<SmsBusinessRulesData> businessRulesOptions = null;
         final Collection<SmsProviderData> smsProviderOptions = null;
         final Collection<EnumOptionData> campaignTypeOptions = null;
@@ -122,7 +125,7 @@ public class SmsCampaignData {
         return new SmsCampaignData(id, campaignName, campaignType, triggerType, runReportId,
                 reportName, paramValue, campaignStatus, message, nextTriggerDate, lastTriggerDate, smsCampaignTimeLine,
                 recurrenceStartDate, recurrence, providerId, businessRulesOptions, smsProviderOptions, campaignTypeOptions,
-                triggerTypeOptions, months, weekDays, frequencyTypeOptions, periodFrequencyOptions);
+                triggerTypeOptions, months, weekDays, frequencyTypeOptions, periodFrequencyOptions, isNotification);
     }
 
     public static SmsCampaignData template(final Collection<SmsProviderData> smsProviderOptions,
@@ -145,10 +148,11 @@ public class SmsCampaignData {
         final EnumOptionData triggerType = null;
         final String reportName = null;
         final Long providerId = null;
+        final boolean isNotification = false;
         return new SmsCampaignData(id, campaignName, campaignType, triggerType, runReportId,
                 reportName, paramValue, campaignStatus, message, nextTriggerDate, lastTriggerDate, smsCampaignTimeLine,
                 recurrenceStartDate, recurrence, providerId, businessRulesOptions, smsProviderOptions, campaignTypeOptions,
-                triggerTypeOptions, months, weekDays, frequencyTypeOptions, periodFrequencyOptions);
+                triggerTypeOptions, months, weekDays, frequencyTypeOptions, periodFrequencyOptions, isNotification);
     }
 
     public Long getId() {
@@ -203,4 +207,8 @@ public class SmsCampaignData {
         return this.providerId;
     }
 
+	public boolean isNotification() {
+		return this.isNotification;
+	}
+
 }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java
index 3e1610e..1c023e4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java
@@ -67,7 +67,7 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
     @Column(name = "campaign_trigger_type", nullable = false)
     private Integer triggerType; //defines direct, scheduled, transaction
     
-    @Column(name = "provider_id", nullable = false)
+    @Column(name = "provider_id", nullable = true)//null for notifications
     private Long providerId; // defined provider details
 
     @ManyToOne
@@ -124,13 +124,16 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
 
     @Column(name = "is_visible", nullable = true)
     private boolean isVisible;
+    
+    @Column(name = "is_notification", nullable = true)
+    private boolean isNotification;
 
     public SmsCampaign() {}
 
     private SmsCampaign(final String campaignName, final Integer campaignType, 
             final Integer triggerType, final Report businessRuleId, final Long providerId, final String paramValue,
             final String message, final LocalDate submittedOnDate, final AppUser submittedBy, final String recurrence,
-            final LocalDateTime localDateTime) {
+            final LocalDateTime localDateTime, final boolean isNotification) {
         this.campaignName = campaignName;
         this.campaignType = campaignType;
         this.triggerType = SmsCampaignTriggerType.fromInt(triggerType).getValue();
@@ -149,6 +152,7 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
         } else {
             this.recurrenceStartDate = recurrenceStartDate.toDate();
         }
+        this.isNotification = isNotification;
     }
 
     public static SmsCampaign instance(final AppUser submittedBy, final Report report, final JsonCommand command) {
@@ -156,7 +160,15 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
         final String campaignName = command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName);
         final Long campaignType = command.longValueOfParameterNamed(SmsCampaignValidator.campaignType);
         final Long triggerType = command.longValueOfParameterNamed(SmsCampaignValidator.triggerType);
-        final Long providerId = command.longValueOfParameterNamed(SmsCampaignValidator.providerId);
+        boolean isNotification = false;
+        if(command.parameterExists(SmsCampaignValidator.isNotificationParamName)){
+        	isNotification = command.booleanPrimitiveValueOfParameterNamed(SmsCampaignValidator.isNotificationParamName);
+        }
+        Long providerId = null;
+        if(!isNotification){
+        	providerId = command.longValueOfParameterNamed(SmsCampaignValidator.providerId);
+        }
+        
         final String paramValue = command.jsonFragment(SmsCampaignValidator.paramValue);
 
         final String message = command.stringValueOfParameterNamed(SmsCampaignValidator.message);
@@ -165,6 +177,7 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
             submittedOnDate = command.localDateValueOfParameterNamed(SmsCampaignValidator.submittedOnDateParamName);
         }
         String recurrence = null;
+        
 
         LocalDateTime recurrenceStartDate = new LocalDateTime();
         if (SmsCampaignTriggerType.fromInt(triggerType.intValue()).isSchedule()) {
@@ -184,7 +197,7 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
         }
 
         return new SmsCampaign(campaignName, campaignType.intValue(), triggerType.intValue(), report,
-                providerId, paramValue, message, submittedOnDate, submittedBy, recurrence, recurrenceStartDate);
+                providerId, paramValue, message, submittedOnDate, submittedBy, recurrence, recurrenceStartDate, isNotification);
     }
 
     public Map<String, Object> update(JsonCommand command) {
@@ -232,6 +245,11 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
             final Long newValue = command.longValueOfParameterNamed(SmsCampaignValidator.providerId);
             actualChanges.put(SmsCampaignValidator.providerId, newValue);
         }
+        if (command.isChangeInBooleanParameterNamed(SmsCampaignValidator.isNotificationParamName, this.isNotification)) {
+            final Boolean newValue = command.booleanObjectValueOfParameterNamed(SmsCampaignValidator.isNotificationParamName);
+            this.isNotification = newValue;
+            actualChanges.put(SmsCampaignValidator.isNotificationParamName, newValue);
+        }
 
         if (SmsCampaignTriggerType.fromInt(this.triggerType).isSchedule()) {
             final String dateFormatAsInput = command.dateFormat();
@@ -558,4 +576,14 @@ public class SmsCampaign extends AbstractPersistableCustom<Long> {
         }
         return recurrenceBuilder.toString();
     }
+
+	public boolean isNotification() {
+		return this.isNotification;
+	}
+
+	public void setNotification(boolean isNotification) {
+		this.isNotification = isNotification;
+	}
+    
+    
 }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
index bc3bb7d..cb1cf1c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
@@ -32,6 +32,8 @@ public interface SmsCampaignRepository extends JpaRepository<SmsCampaign, Long>,
 
     Collection<SmsCampaign> findByCampaignTypeAndTriggerTypeAndStatus(final Integer campaignType, final Integer triggerType,
             final Integer status);
+    
+    Collection<SmsCampaign> findByTriggerTypeAndStatus(final Integer triggerType, final Integer status);
 
     Collection<SmsCampaign> findByTriggerType(final Integer triggerType) ;
     

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java
index dcc9a77..d9eb56f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java
@@ -28,12 +28,16 @@ import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
 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.gcm.domain.DeviceRegistration;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationRepositoryWrapper;
 import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType;
+import org.apache.fineract.portfolio.client.domain.Client;
 import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -67,19 +71,21 @@ public class SmsCampaignValidator {
     public static final String frequencyParamName = "frequency";
     public static final String intervalParamName = "interval";
     public static final String repeatsOnDayParamName = "repeatsOnDay";
+    public static final String isNotificationParamName = "isNotification";
 
     private final FromJsonHelper fromApiJsonHelper;
+    private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository;
 
     protected static final Set<String> supportedParams = new HashSet<>(Arrays.asList(campaignName, campaignType,
             localeParamName,
             dateFormatParamName, runReportId, paramValue, message, recurrenceStartDate, activationDateParamName, submittedOnDateParamName,
             closureDateParamName, recurrenceParamName, providerId, triggerType, frequencyParamName, intervalParamName,
-            repeatsOnDayParamName, triggerEntityType, triggerActionType, dateTimeFormat));
+            repeatsOnDayParamName, triggerEntityType, triggerActionType, dateTimeFormat, isNotificationParamName));
 
     protected static final Set<String> supportedParamsForUpdate = new HashSet<>(Arrays.asList(campaignName, campaignType,
             localeParamName,
             dateFormatParamName, runReportId, paramValue, message, recurrenceStartDate, activationDateParamName, recurrenceParamName,
-            providerId, triggerType, triggerEntityType, triggerActionType, dateTimeFormat));
+            providerId, triggerType, triggerEntityType, triggerActionType, dateTimeFormat, isNotificationParamName));
 
     protected static final Set<String> ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
             dateFormatParamName,
@@ -92,8 +98,9 @@ public class SmsCampaignValidator {
     protected static final Set<String> PREVIEW_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(paramValue, message));
 
     @Autowired
-    public SmsCampaignValidator(FromJsonHelper fromApiJsonHelper) {
+    public SmsCampaignValidator(FromJsonHelper fromApiJsonHelper, final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository) {
         this.fromApiJsonHelper = fromApiJsonHelper;
+        this.deviceRegistrationRepository = deviceRegistrationRepository;
     }
 
     public void validateCreate(String json) {
@@ -159,6 +166,12 @@ public class SmsCampaignValidator {
                     element);
             baseDataValidator.reset().parameter(SmsCampaignValidator.submittedOnDateParamName).value(submittedOnDate).notNull();
         }
+        
+        if (this.fromApiJsonHelper.parameterExists(SmsCampaignValidator.isNotificationParamName, element)) {
+            final Boolean isNotification = this.fromApiJsonHelper.extractBooleanNamed(SmsCampaignValidator.isNotificationParamName,
+                    element);
+            baseDataValidator.reset().parameter(SmsCampaignValidator.submittedOnDateParamName).trueOrFalseRequired(isNotification);
+        }
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
 
     }
@@ -214,7 +227,11 @@ public class SmsCampaignValidator {
                 }
             }
         }
-
+        if (this.fromApiJsonHelper.parameterExists(SmsCampaignValidator.isNotificationParamName, element)) {
+            final Boolean isNotification = this.fromApiJsonHelper.extractBooleanNamed(SmsCampaignValidator.isNotificationParamName,
+                    element);
+            baseDataValidator.reset().parameter(SmsCampaignValidator.submittedOnDateParamName).trueOrFalseRequired(isNotification);
+        }
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
 
     }
@@ -303,4 +320,17 @@ public class SmsCampaignValidator {
     private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
         if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
     }
+    
+	public boolean isValidNotificationOrSms(Client client, SmsCampaign smsCampaign, Object mobileNo) {
+		if (smsCampaign.isNotification()) {
+			if (client != null) {
+				DeviceRegistration deviceRegistration = this.deviceRegistrationRepository
+						.findDeviceRegistrationByClientId(client.getId());
+				return (deviceRegistration != null);
+			}
+			return false;
+		}
+		return (mobileNo != null);
+	}
+    
 }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
index 3b0e0ae..7bf79ad 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
@@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTri
 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.SmsRuntimeException;
+import org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator;
 import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
 import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
 import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
@@ -77,13 +78,15 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
     private final GroupRepository groupRepository;
 
     private final SmsMessageScheduledJobService smsMessageScheduledJobService; 
+    private final SmsCampaignValidator smsCampaignValidator;
     
     @Autowired
     public SmsCampaignDomainServiceImpl(final SmsCampaignRepository smsCampaignRepository, final SmsMessageRepository smsMessageRepository,
                                         final BusinessEventNotifierService businessEventNotifierService, final OfficeRepository officeRepository,
                                         final SmsCampaignWritePlatformService smsCampaignWritePlatformCommandHandler,
                                         final GroupRepository groupRepository,
-                                        final SmsMessageScheduledJobService smsMessageScheduledJobService){
+                                        final SmsMessageScheduledJobService smsMessageScheduledJobService,
+                                        final SmsCampaignValidator smsCampaignValidator){
         this.smsCampaignRepository = smsCampaignRepository;
         this.smsMessageRepository = smsMessageRepository;
         this.businessEventNotifierService = businessEventNotifierService;
@@ -91,6 +94,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
         this.smsCampaignWritePlatformCommandHandler = smsCampaignWritePlatformCommandHandler;
         this.groupRepository = groupRepository;
         this.smsMessageScheduledJobService = smsMessageScheduledJobService ;
+        this.smsCampaignValidator = smsCampaignValidator;
     }
 
     @PostConstruct
@@ -217,10 +221,13 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
 							String message = this.smsCampaignWritePlatformCommandHandler.compileSmsTemplate(
 									smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams);
 							Object mobileNo = smsParams.get("mobileNo");
-							if (mobileNo != null) {
+							if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
+								String mobileNumber = null;
+		                    	if(mobileNo != null){
+		                    		mobileNumber = mobileNo.toString();
+		                    	}
 								SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message,
-										mobileNo.toString(), smsCampaign);
-								this.smsMessageRepository.save(smsMessage);
+										mobileNumber, smsCampaign, smsCampaign.isNotification());
 								Collection<SmsMessage> messages = new ArrayList<>();
 								messages.add(smsMessage);
 								Map<SmsCampaign, Collection<SmsMessage>> smsDataMap = new HashMap<>();
@@ -273,9 +280,13 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService {
 					String message = this.smsCampaignWritePlatformCommandHandler
 							.compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams);
 					Object mobileNo = smsParams.get("mobileNo");
-					if (mobileNo != null) {
+					if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
+						String mobileNumber = null;
+                    	if(mobileNo != null){
+                    		mobileNumber = mobileNo.toString();
+                    	}
 						SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message,
-								mobileNo.toString(), smsCampaign);
+								mobileNumber, smsCampaign, smsCampaign.isNotification());
 						this.smsMessageRepository.save(smsMessage);
 						Collection<SmsMessage> messages = new ArrayList<>();
 						messages.add(smsMessage);

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java
index d974d7a..69536f8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java
@@ -30,7 +30,6 @@ import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
 import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignEnumerations;
 import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
 import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData;
-import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType;
 import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations;
@@ -81,10 +80,8 @@ public class SmsCampaignDropdownReadPlatformServiceImpl implements SmsCampaignDr
                     new ParameterizedTypeReference<Collection<SmsProviderData>>() {});
             smsProviderOptions = responseOne.getBody();
             if (!responseOne.getStatusCode().equals(HttpStatus.OK)) {
-                throw new ConnectionFailureException(hostName);
             }
         } catch (Exception e) {
-        	 throw new ConnectionFailureException(hostName);
         }
         return smsProviderOptions;
     }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java
index 94215b0..5e15272 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java
@@ -219,7 +219,8 @@ public class SmsCampaignReadPlatformServiceImpl implements SmsCampaignReadPlatfo
             sql.append("acu.username as activatedByUsername, ");
             sql.append("sc.approvedon_date as activatedOnDate, ");
             sql.append("sr.report_name as reportName, ");
-            sql.append("provider_id as providerId ");
+            sql.append("provider_id as providerId, ");
+            sql.append("sc.is_notification as isNotification ");
             sql.append("from sms_campaign sc ");
             sql.append("left join m_appuser sbu on sbu.id = sc.submittedon_userid ");
             sql.append("left join m_appuser acu on acu.id = sc.approvedon_userid ");
@@ -265,9 +266,9 @@ public class SmsCampaignReadPlatformServiceImpl implements SmsCampaignReadPlatfo
                     activatedByUsername, closedOnDate, closedByUsername);
             final String reportName = rs.getString("reportName");
             final Long providerId = rs.getLong("providerId");
-
+            final Boolean isNotification = rs.getBoolean("isNotification");
             return SmsCampaignData.instance(id, campaignName, campaignTypeEnum, triggerTypeEnum, runReportId, reportName, paramValue, status, message, nextTriggerDate, lastTriggerDate,
-                    smsCampaignTimeLine, recurrenceStartDate, recurrence, providerId);
+                    smsCampaignTimeLine, recurrenceStartDate, recurrence, providerId, isNotification);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/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
index 0084ed6..976a6d7 100644
--- 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
@@ -30,7 +30,6 @@ 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;
@@ -56,6 +55,8 @@ 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.gcm.domain.DeviceRegistration;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationRepositoryWrapper;
 import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
 import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
@@ -109,6 +110,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
     private final ReadReportingService readReportingService;
     private final GenericDataService genericDataService;
     private final FromJsonHelper fromJsonHelper;
+    private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository;
 
     private final SmsMessageScheduledJobService smsMessageScheduledJobService;
     
@@ -118,7 +120,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
             final SmsMessageRepository smsMessageRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
             final ReadReportingService readReportingService, final GenericDataService genericDataService,
             final FromJsonHelper fromJsonHelper, final GroupRepository groupRepository,
-            final SmsMessageScheduledJobService smsMessageScheduledJobService) {
+            final SmsMessageScheduledJobService smsMessageScheduledJobService, final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository) {
         this.context = context;
         this.smsCampaignRepository = smsCampaignRepository;
         this.smsCampaignValidator = smsCampaignValidator;
@@ -130,6 +132,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
         this.fromJsonHelper = fromJsonHelper;
         this.groupRepository = groupRepository;
         this.smsMessageScheduledJobService = smsMessageScheduledJobService ;
+        this.deviceRegistrationRepository = deviceRegistrationRepository;
     }
 
     @Transactional
@@ -228,10 +231,14 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
                     Object mobileNo = entry.get("mobileNo");
 
                     Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
-                    if (mobileNo != null) {
+                    if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
 //                        String countryCode = this.smsReadPlatformService.retrieveCountryCode(client.getOffice().getId()).getCountryCode();
-                        SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNo.toString(),
-                                smsCampaign);
+                    	String mobileNumber = null;
+                    	if(mobileNo != null){
+                    		mobileNumber = mobileNo.toString();
+                    	}
+                        SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNumber,
+                                smsCampaign, smsCampaign.isNotification());
                         this.smsMessageRepository.save(smsMessage);
                     }
                 }
@@ -242,6 +249,8 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
 
     }
 
+
+
     @Override
     public void insertDirectCampaignIntoSmsOutboundTable(final Loan loan, final SmsCampaign smsCampaign) {
         try {
@@ -278,9 +287,13 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
                         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);
+                        if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
+                        	String mobileNumber = null;
+                        	if(mobileNo != null){
+                        		mobileNumber = mobileNo.toString();
+                        	}
+                            SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNumber,
+                                    smsCampaign, smsCampaign.isNotification());
                             smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
                             this.smsMessageRepository.save(smsMessage);
                             Collection<SmsMessage> messages = new ArrayList<>() ;
@@ -320,9 +333,13 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
 							smsCampaign.getCampaignName(), entry);
 					Object mobileNo = entry.get("mobileNo");
 
-					if (mobileNo != null) {
+					if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
+						String mobileNumber = null;
+                    	if(mobileNo != null){
+                    		mobileNumber = mobileNo.toString();
+                    	}
 						SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage,
-								mobileNo.toString(), smsCampaign);
+								mobileNumber, smsCampaign, smsCampaign.isNotification());
 						smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
 						this.smsMessageRepository.save(smsMessage);
 						Collection<SmsMessage> messages = new ArrayList<>();
@@ -360,9 +377,13 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
 							smsCampaign.getCampaignName(), entry);
 					Object mobileNo = entry.get("mobileNo");
 
-					if (mobileNo != null) {
+					if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
+						String mobileNumber = null;
+                    	if(mobileNo != null){
+                    		mobileNumber = mobileNo.toString();
+                    	}
 						SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage,
-								mobileNo.toString(), smsCampaign);
+								mobileNumber, smsCampaign, smsCampaign.isNotification());
 						smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
 						this.smsMessageRepository.save(smsMessage);
 						Collection<SmsMessage> messages = new ArrayList<>();
@@ -634,8 +655,8 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
     @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());
+        final Collection<SmsCampaign> smsCampaignDataCollection = this.smsCampaignRepository.findByTriggerTypeAndStatus(
+                SmsCampaignTriggerType.SCHEDULE.getValue(), SmsCampaignStatus.ACTIVE.getValue());
         if (smsCampaignDataCollection != null) {
             for (SmsCampaign smsCampaign : smsCampaignDataCollection) {
                 LocalDateTime tenantDateNow = tenantDateTime();

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/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 9b5aeb2..3168ef4 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
@@ -24,6 +24,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.NOTIFICATION_JSON_INPUT_PARAMS;
 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;
@@ -40,6 +41,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 Set<String> NotificationSupportedParameters = NOTIFICATION_JSON_INPUT_PARAMS.getAllValues();
     private final FromJsonHelper fromApiJsonHelper;
 
     @Autowired
@@ -64,6 +66,10 @@ public class ExternalServicesPropertiesCommandFromApiJsonDeserializer {
                 this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.SMSSupportedParameters);
             break;
 
+            case "NOTIFICATION":
+                this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.NotificationSupportedParameters);
+            break;
+
             default:
                 throw new ExternalServiceConfigurationNotFoundException(externalServiceName);
         }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/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 44a56ca..f123c19 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
@@ -41,6 +41,11 @@ public class ExternalServicesConstants {
     public static final String SMS_END_POINT = "end_point";
     public static final String SMS_TENANT_APP_KEY = "tenant_app_key";
     
+    public static final String NOTIFICATION_SERVICE_NAME = "NOTIFICATION";
+    public static final String NOTIFICATION_SERVER_KEY = "server_key";
+    public static final String NOTIFICATION_GCM_END_POINT = "gcm_end_point";
+    public static final String NOTIFICATION_FCM_END_POINT = "fcm_end_point";
+    
     public static enum EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS {
         EXTERNAL_SERVICE_ID("external_service_id"), NAME("name"), VALUE("value");
 
@@ -164,5 +169,36 @@ public class ExternalServicesConstants {
             return this.value;
         }
     }
+    
+    public static enum NOTIFICATION_JSON_INPUT_PARAMS {
+        SERVER_KEY("server_key"), GCM_END_POINT("gcm_end_point"), FCM_END_POINT("fcm_end_point");
+
+        private final String value;
+
+        private NOTIFICATION_JSON_INPUT_PARAMS(final String value) {
+            this.value = value;
+        }
+
+        private static final Set<String> values = new HashSet<>();
+
+        static {
+            for (final NOTIFICATION_JSON_INPUT_PARAMS type : NOTIFICATION_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;
+        }
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/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 2d46835..25f4b8e 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
@@ -24,6 +24,7 @@ import org.apache.fineract.infrastructure.campaigns.sms.data.MessageGatewayConfi
 import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData;
 import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData;
 import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData;
+import org.apache.fineract.infrastructure.gcm.domain.NotificationConfigurationData;
 
 public interface ExternalServicesPropertiesReadPlatformService {
 
@@ -34,5 +35,7 @@ public interface ExternalServicesPropertiesReadPlatformService {
     MessageGatewayConfigurationData getSMSGateway();
 
     Collection<ExternalServicesPropertiesData> retrieveOne(String serviceName);
+    
+    NotificationConfigurationData getNotificationConfiguration();
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/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 ccd4012..bf62708 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
@@ -28,6 +28,7 @@ import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData;
 import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData;
 import org.apache.fineract.infrastructure.configuration.exception.ExternalServiceConfigurationNotFoundException;
 import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.gcm.domain.NotificationConfigurationData;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -176,6 +177,10 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
                 serviceNameToUse = ExternalServicesConstants.SMS_SERVICE_NAME;
             break;
 
+            case "NOTIFICATION":
+                serviceNameToUse = ExternalServicesConstants.NOTIFICATION_SERVICE_NAME;
+            break;
+
             default:
                 throw new ExternalServiceConfigurationNotFoundException(serviceName);
         }
@@ -185,5 +190,35 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
         return this.jdbcTemplate.query(sql, mapper, new Object[] {});
 
     }
+    
+    private static final class NotificationDataExtractor implements ResultSetExtractor<NotificationConfigurationData> {
+
+        @Override
+        public NotificationConfigurationData extractData(final ResultSet rs) throws SQLException, DataAccessException {
+            String serverKey = null;
+            String gcmEndPoint = null;
+            String fcmEndPoint = null;
+            while (rs.next()) {
+                if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.NOTIFICATION_SERVER_KEY )) {
+                	serverKey = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.NOTIFICATION_GCM_END_POINT )) {
+                	gcmEndPoint = rs.getString("value");
+                } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.NOTIFICATION_FCM_END_POINT )) {
+                	fcmEndPoint = rs.getString("value");
+                }
+            }
+            return new NotificationConfigurationData(null, serverKey, gcmEndPoint, fcmEndPoint);
+        }
+    }
+
+
+	@Override
+	public NotificationConfigurationData getNotificationConfiguration() {
+		final ResultSetExtractor<NotificationConfigurationData> resultSetExtractor = new NotificationDataExtractor();
+        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.NOTIFICATION_SERVICE_NAME + "'";
+        final NotificationConfigurationData notificationConfigurationData = this.jdbcTemplate.query(sql, resultSetExtractor, new Object[] {});
+        return notificationConfigurationData;
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/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 44f4164..9bab493 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
@@ -58,6 +58,10 @@ public class ExternalServicesReadPlatformServiceImpl implements ExternalServices
                 serviceNameToUse = ExternalServicesConstants.SMS_SERVICE_NAME;
             break;
 
+            case "NOTIFICATION":
+                serviceNameToUse = ExternalServicesConstants.NOTIFICATION_SERVICE_NAME;
+            break;
+
             default:
                 throw new ExternalServiceConfigurationNotFoundException(serviceName);
         }

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/GcmConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/GcmConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/GcmConstants.java
new file mode 100644
index 0000000..fa3b9bb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/GcmConstants.java
@@ -0,0 +1,242 @@
+/**
+ * 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.gcm;
+
+/**
+ * Constants used on GCM service communication.
+ */
+public final class GcmConstants {
+	
+	
+	/**
+	 * Title for notification
+	 */	
+	public static final String title = "Hello !";
+	
+	/**
+	 * icon for notification.
+	 */
+	public static final String defaultIcon = "default";
+
+	
+	/**
+	 * Parameter for to field.
+	 */
+	public static final String PARAM_TO = "to";
+
+	/**
+	 * Prefix of the topic.
+	 */
+	public static final String TOPIC_PREFIX = "/topics/";
+
+	/**
+	 * HTTP parameter for registration id.
+	 */
+	//public static final String PARAM_REGISTRATION_ID = "registration_id";
+
+	/**
+	 * HTTP parameter for collapse key.
+	 */
+	public static final String PARAM_COLLAPSE_KEY = "collapse_key";
+
+	/**
+	 * HTTP parameter for delaying the message delivery if the device is idle.
+	 */
+	public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";
+
+	/**
+	 * HTTP parameter for telling gcm to validate the message without actually
+	 * sending it.
+	 */
+	public static final String PARAM_DRY_RUN = "dry_run";
+
+	/**
+	 * HTTP parameter for package name that can be used to restrict message
+	 * delivery by matching against the package name used to generate the
+	 * registration id.
+	 */
+	public static final String PARAM_RESTRICTED_PACKAGE_NAME = "restricted_package_name";
+
+	/**
+	 * Parameter used to set the message time-to-live.
+	 */
+	public static final String PARAM_TIME_TO_LIVE = "time_to_live";
+
+	/**
+	 * Parameter used to set the message priority.
+	 */
+	public static final String PARAM_PRIORITY = "priority";
+
+	/**
+	 * Parameter used to set the content available (iOS only)
+	 */
+	public static final String PARAM_CONTENT_AVAILABLE = "content_available";
+
+	/**
+	 * Value used to set message priority to normal.
+	 */
+	public static final String MESSAGE_PRIORITY_NORMAL = "normal";
+
+	/**
+	 * Value used to set message priority to high.
+	 */
+	public static final String MESSAGE_PRIORITY_HIGH = "high";
+	
+	/**
+	 * A particular message could not be sent because the GCM servers were not
+	 * available. Used only on JSON requests, as in plain text requests
+	 * unavailability is indicated by a 503 response.
+	 */
+	public static final String ERROR_UNAVAILABLE = "Unavailable";
+
+	/**
+	 * A particular message could not be sent because the GCM servers
+	 * encountered an error. Used only on JSON requests, as in plain text
+	 * requests internal errors are indicated by a 500 response.
+	 */
+	public static final String ERROR_INTERNAL_SERVER_ERROR = "InternalServerError";
+
+	/**
+	 * Token returned by GCM when the requested registration id has a canonical
+	 * value.
+	 */
+	public static final String TOKEN_CANONICAL_REG_ID = "registration_id";
+
+	/**
+	 * JSON-only field representing the registration ids.
+	 */
+	public static final String JSON_REGISTRATION_IDS = "registration_ids";
+
+	/**
+	 * JSON-only field representing the to recipient.
+	 */
+	public static final String JSON_TO = "to";
+
+	/**
+	 * JSON-only field representing the payload data.
+	 */
+	public static final String JSON_PAYLOAD = "data";
+
+	/**
+	 * JSON-only field representing the notification payload.
+	 */
+	public static final String JSON_NOTIFICATION = "notification";
+
+	/**
+	 * JSON-only field representing the notification title.
+	 */
+	public static final String JSON_NOTIFICATION_TITLE = "title";
+
+	/**
+	 * JSON-only field representing the notification body.
+	 */
+	public static final String JSON_NOTIFICATION_BODY = "body";
+
+	/**
+	 * JSON-only field representing the notification icon.
+	 */
+	public static final String JSON_NOTIFICATION_ICON = "icon";
+
+	/**
+	 * JSON-only field representing the notification sound.
+	 */
+	public static final String JSON_NOTIFICATION_SOUND = "sound";
+
+	/**
+	 * JSON-only field representing the notification badge.
+	 */
+	public static final String JSON_NOTIFICATION_BADGE = "badge";
+
+	/**
+	 * JSON-only field representing the notification tag.
+	 */
+	public static final String JSON_NOTIFICATION_TAG = "tag";
+
+	/**
+	 * JSON-only field representing the notification color.
+	 */
+	public static final String JSON_NOTIFICATION_COLOR = "color";
+
+	/**
+	 * JSON-only field representing the notification click action.
+	 */
+	public static final String JSON_NOTIFICATION_CLICK_ACTION = "click_action";
+
+	/**
+	 * JSON-only field representing the notification body localization key.
+	 */
+	public static final String JSON_NOTIFICATION_BODY_LOC_KEY = "body_loc_key";
+
+	/**
+	 * JSON-only field representing the notification body localization values.
+	 */
+	public static final String JSON_NOTIFICATION_BODY_LOC_ARGS = "body_loc_args";
+
+	/**
+	 * JSON-only field representing the notification title localization key.
+	 */
+	public static final String JSON_NOTIFICATION_TITLE_LOC_KEY = "title_loc_key";
+
+	/**
+	 * JSON-only field representing the notification title localization values.
+	 */
+	public static final String JSON_NOTIFICATION_TITLE_LOC_ARGS = "title_loc_args";
+
+	/**
+	 * JSON-only field representing the number of successful messages.
+	 */
+	public static final String JSON_SUCCESS = "success";
+
+	/**
+	 * JSON-only field representing the number of failed messages.
+	 */
+	public static final String JSON_FAILURE = "failure";
+
+	/**
+	 * JSON-only field representing the number of messages with a canonical
+	 * registration id.
+	 */
+	public static final String JSON_CANONICAL_IDS = "canonical_ids";
+
+	/**
+	 * JSON-only field representing the id of the multicast request.
+	 */
+	public static final String JSON_MULTICAST_ID = "multicast_id";
+
+	/**
+	 * JSON-only field representing the result of each individual request.
+	 */
+	public static final String JSON_RESULTS = "results";
+
+	/**
+	 * JSON-only field representing the error field of an individual request.
+	 */
+	public static final String JSON_ERROR = "error";
+
+	/**
+	 * JSON-only field sent by GCM when a message was successfully sent.
+	 */
+	public static final String JSON_MESSAGE_ID = "message_id";
+
+	private GcmConstants() {
+		throw new UnsupportedOperationException();
+	}
+	
+	public static final Integer TIME_TO_LIVE = 30;
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiConstants.java
new file mode 100644
index 0000000..0b675cb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiConstants.java
@@ -0,0 +1,25 @@
+/**
+ * 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.gcm.api;
+
+public class DeviceRegistrationApiConstants {
+	public static final String clientIdParamName = "clientId";
+	public static final String registrationIdParamName = "registrationId";
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiResource.java
new file mode 100644
index 0000000..1ba0fa7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/api/DeviceRegistrationApiResource.java
@@ -0,0 +1,160 @@
+/**
+ * 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.gcm.api;
+
+import java.util.Collection;
+import java.util.HashMap;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistration;
+import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationData;
+import org.apache.fineract.infrastructure.gcm.service.DeviceRegistrationReadPlatformService;
+import org.apache.fineract.infrastructure.gcm.service.DeviceRegistrationWritePlatformService;
+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 com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+@Path("/device/registration")
+@Component
+@Scope("singleton")
+public class DeviceRegistrationApiResource {
+
+    private final PlatformSecurityContext context;
+    private final DeviceRegistrationWritePlatformService deviceRegistrationWritePlatformService;
+    private final DefaultToApiJsonSerializer<DeviceRegistrationData> toApiJsonSerializer;
+    private final DeviceRegistrationReadPlatformService deviceRegistrationReadPlatformService;
+
+    @Autowired
+    public DeviceRegistrationApiResource(PlatformSecurityContext context,
+            final DefaultToApiJsonSerializer<DeviceRegistrationData> toApiJsonSerializer,
+            final DeviceRegistrationReadPlatformService deviceRegistrationReadPlatformService,
+            final DeviceRegistrationWritePlatformService deviceRegistrationWritePlatformService) {
+        this.context = context;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.deviceRegistrationReadPlatformService = deviceRegistrationReadPlatformService;
+        this.deviceRegistrationWritePlatformService = deviceRegistrationWritePlatformService;
+    }
+
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String registerDevice(final String apiRequestBodyAsJson) {
+        this.context.authenticatedUser();
+        Gson gson = new Gson();
+        JsonObject json = new Gson().fromJson(apiRequestBodyAsJson, JsonObject.class);
+        Long clientId = json.get(DeviceRegistrationApiConstants.clientIdParamName).getAsLong();
+        String registrationId = json.get(DeviceRegistrationApiConstants.registrationIdParamName).getAsString();
+        DeviceRegistration deviceRegistration = this.deviceRegistrationWritePlatformService.registerDevice(clientId, registrationId);
+        String response = gson.toJson(deviceRegistration);
+        return response;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAllDeviceRegistrations(@Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser();
+
+        Collection<DeviceRegistrationData> deviceRegistrationDataList = this.deviceRegistrationReadPlatformService
+                .retrieveAllDeviceRegiistrations();
+
+        return this.toApiJsonSerializer.serialize(deviceRegistrationDataList);
+    }
+
+    @GET
+    @Path("client/{clientId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveDeviceRegistrationByClientId(@PathParam("clientId") final Long clientId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser();
+
+        DeviceRegistrationData deviceRegistrationData = this.deviceRegistrationReadPlatformService
+                .retrieveDeviceRegiistrationByClientId(clientId);
+
+        return this.toApiJsonSerializer.serialize(deviceRegistrationData);
+    }
+
+    @GET
+    @Path("{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveDeviceRegiistration(@PathParam("id") final Long id, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser();
+
+        DeviceRegistrationData deviceRegistrationData = this.deviceRegistrationReadPlatformService.retrieveDeviceRegiistration(id);
+
+        return this.toApiJsonSerializer.serialize(deviceRegistrationData);
+    }
+
+    @PUT
+    @Path("{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String updateDeviceRegistration(@PathParam("id") final Long id, final String apiRequestBodyAsJson) {
+
+        this.context.authenticatedUser();
+
+        Gson gson = new Gson();
+        JsonObject json = new Gson().fromJson(apiRequestBodyAsJson, JsonObject.class);
+        Long clientId = json.get(DeviceRegistrationApiConstants.clientIdParamName).getAsLong();
+        String registrationId = json.get(DeviceRegistrationApiConstants.registrationIdParamName).getAsString();
+        DeviceRegistration deviceRegistration = this.deviceRegistrationWritePlatformService.updateDeviceRegistration(id, clientId,
+                registrationId);
+        String response = gson.toJson(deviceRegistration);
+        return response;
+    }
+
+    @DELETE
+    @Path("{id}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String delete(@PathParam("id") final Long id) {
+        
+        this.context.authenticatedUser();
+        this.deviceRegistrationWritePlatformService.deleteDeviceRegistration(id);
+        return responseMap(id);
+        
+    }
+    
+    public String responseMap(Long id){
+        HashMap<String, Object> responseMap = new HashMap<>();
+        responseMap.put("resource", id);
+        return new Gson().toJson(responseMap);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistration.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistration.java
new file mode 100644
index 0000000..c4ba7b3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistration.java
@@ -0,0 +1,85 @@
+/**
+ * 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.gcm.domain;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.client.domain.Client;
+
+@Entity
+@Table(name = "client_device_registration")
+public class DeviceRegistration extends AbstractPersistableCustom<Long> {
+
+	@OneToOne
+	@JoinColumn(name = "client_id", nullable = false, unique = true)
+	private Client client;
+
+	@Column(name = "registration_id", nullable = false, unique = true)
+	private String registrationId;
+
+	@Column(name = "updatedon_date", nullable = false)
+	@Temporal(TemporalType.TIMESTAMP)
+	private Date updatedOnDate;
+
+	private DeviceRegistration(final Client client, final String registrationId) {
+		this.client = client;
+		this.registrationId = registrationId;
+		this.updatedOnDate = DateUtils.getLocalDateTimeOfTenant().toDate();
+	}
+
+	public static DeviceRegistration instance(final Client client,
+			final String registrationId) {
+		return new DeviceRegistration(client, registrationId);
+	}
+
+	public Client getClient() {
+		return this.client;
+	}
+
+	public void setClient(Client client) {
+		this.client = client;
+	}
+
+	public String getRegistrationId() {
+		return this.registrationId;
+	}
+
+	public void setRegistrationId(String registrationId) {
+		this.registrationId = registrationId;
+	}
+
+	public Date getUpdatedOnDate() {
+		return this.updatedOnDate;
+	}
+
+	public void setUpdatedOnDate(Date updatedOnDate) {
+		this.updatedOnDate = updatedOnDate;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationData.java
new file mode 100644
index 0000000..3397a9b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationData.java
@@ -0,0 +1,47 @@
+/**
+ * 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.gcm.domain;
+
+import java.util.Date;
+
+import org.apache.fineract.portfolio.client.data.ClientData;
+
+public class DeviceRegistrationData {
+
+	public Long id;
+	public ClientData clientData;
+	public String registrationId;
+	public Date updatedOnDate;
+
+	private DeviceRegistrationData(final Long id, final ClientData clientData,
+			final String registrationId, final Date updatedOnDate) {
+		this.id = id;
+		this.clientData = clientData;
+		this.registrationId = registrationId;
+		this.updatedOnDate = updatedOnDate;
+	}
+
+	public static DeviceRegistrationData instance(final Long id,
+			final ClientData clientData, final String registrationId,
+			final Date updatedOnDate) {
+		return new DeviceRegistrationData(id, clientData, registrationId,
+				updatedOnDate);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepository.java
new file mode 100644
index 0000000..0033117
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepository.java
@@ -0,0 +1,36 @@
+/**
+ * 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.gcm.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface DeviceRegistrationRepository extends
+		JpaRepository<DeviceRegistration, Long>,
+		JpaSpecificationExecutor<DeviceRegistration> {
+
+	public static final String FIND_DEVICE_REGISTRATION_BY_CLIENT = "select dr from DeviceRegistration dr where dr.client.id =:clientId ";
+
+	@Query(FIND_DEVICE_REGISTRATION_BY_CLIENT)
+	DeviceRegistration findDeviceRegistrationByClientId(
+			@Param("clientId") Long clientId);
+
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/8f30c210/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepositoryWrapper.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepositoryWrapper.java
new file mode 100644
index 0000000..a36b476
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/gcm/domain/DeviceRegistrationRepositoryWrapper.java
@@ -0,0 +1,61 @@
+/**
+ * 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.gcm.domain;
+
+import org.apache.fineract.infrastructure.gcm.exception.DeviceRegistrationNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DeviceRegistrationRepositoryWrapper {
+
+	private final DeviceRegistrationRepository repository;
+
+	@Autowired
+	public DeviceRegistrationRepositoryWrapper(
+			DeviceRegistrationRepository repository) {
+		this.repository = repository;
+	}
+
+	public DeviceRegistration findOneWithNotFoundDetection(
+			final Long deviceRegistrationId) {
+		final DeviceRegistration deviceRegistration = this.repository
+				.findOne(deviceRegistrationId);
+		if (deviceRegistration == null) {
+			throw new DeviceRegistrationNotFoundException(deviceRegistrationId);
+		}
+		return deviceRegistration;
+	}
+
+	public void save(final DeviceRegistration deviceRegistration) {
+		this.repository.save(deviceRegistration);
+	}
+
+	public void delete(final DeviceRegistration deviceRegistration) {
+		this.repository.delete(deviceRegistration);
+	}
+
+	public void saveAndFlush(final DeviceRegistration deviceRegistration) {
+		this.repository.saveAndFlush(deviceRegistration);
+	}
+
+	public DeviceRegistration findDeviceRegistrationByClientId(Long clientId) {
+		return this.repository.findDeviceRegistrationByClientId(clientId);
+	}
+}