fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nazeer1100...@apache.org
Subject [2/4] fineract git commit: notification sms
Date Mon, 20 Nov 2017 10:29:18 GMT
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);
+
+	}
+
+}


Mime
View raw message