Merge branch 'PR421' into develop
This commit is contained in:
+11
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -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;
|
||||
}
|
||||
|
||||
+12
-4
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+32
-4
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -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) ;
|
||||
|
||||
|
||||
+34
-4
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-6
@@ -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);
|
||||
|
||||
-3
@@ -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;
|
||||
}
|
||||
|
||||
+4
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-14
@@ -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();
|
||||
|
||||
+6
@@ -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);
|
||||
}
|
||||
|
||||
+36
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -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();
|
||||
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
|
||||
+242
@@ -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;
|
||||
}
|
||||
+25
@@ -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";
|
||||
|
||||
}
|
||||
+160
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+85
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -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);
|
||||
|
||||
}
|
||||
+61
@@ -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);
|
||||
}
|
||||
}
|
||||
+317
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+151
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+330
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+187
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+832
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -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);
|
||||
}
|
||||
+125
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -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);
|
||||
}
|
||||
+123
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+133
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+23
-9
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+3
-1
@@ -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) {
|
||||
|
||||
+24
-12
@@ -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) {
|
||||
|
||||
+6
@@ -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,
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
+48
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user