diff --git a/mifosng-db/migrations/core_db/V126__initial_database_structure_for_sms_outbound.sql b/mifosng-db/migrations/core_db/V126__initial_database_structure_for_sms_outbound.sql new file mode 100644 index 000000000..0d2a74798 --- /dev/null +++ b/mifosng-db/migrations/core_db/V126__initial_database_structure_for_sms_outbound.sql @@ -0,0 +1,36 @@ +DROP TABLE IF EXISTS `sms_messages_outbound`; +CREATE TABLE `sms_messages_outbound` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `group_id` bigint(20) DEFAULT NULL, + `client_id` bigint(20) DEFAULT NULL, + `staff_id` bigint(20) DEFAULT NULL, + `status_enum` int(5) NOT NULL DEFAULT '100', + `mobile_no` varchar(50) NOT NULL, + `message` varchar(1000) NOT NULL, + PRIMARY KEY (`id`), + KEY `FKGROUP000000001` (`group_id`), + KEY `FKCLIENT00000001` (`client_id`), + CONSTRAINT `FKGROUP000000001` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`), + CONSTRAINT `FKCLIENT00000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`), + CONSTRAINT `FKSTAFF000000001` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Permissions for CRUD on SMS outbound message tracking +DELETE FROM `m_permission` WHERE `entity_name`='SMS'; +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organisation', 'READ_SMS', 'SMS', 'READ', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organisation', 'CREATE_SMS', 'SMS', 'CREATE', 0); +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organisation', 'CREATE_SMS_CHECKER', 'SMS', 'CREATE', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organisation', 'UPDATE_SMS', 'SMS', 'UPDATE', 0); +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organisation', 'UPDATE_SMS_CHECKER', 'SMS', 'UPDATE', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organisation', 'DELETE_SMS', 'SMS', 'DELETE', 0); +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organisation', 'DELETE_SMS_CHECKER', 'SMS', 'DELETE', 0); \ No newline at end of file diff --git a/mifosng-provider/gradlew b/mifosng-provider/gradlew index 91a7e269e..dabadcd00 100755 --- a/mifosng-provider/gradlew +++ b/mifosng-provider/gradlew @@ -7,7 +7,10 @@ ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8005,server=y,suspend=n" + +CATALINA_OPTS="-Xms512m -Xmx512m" +JAVA_OPTS="-Xms512m -Xmx512m -XX:PermSize=256m -XX:MaxPermSize=512m" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` diff --git a/mifosng-provider/gradlew.bat b/mifosng-provider/gradlew.bat index aec99730b..af905cc44 100644 --- a/mifosng-provider/gradlew.bat +++ b/mifosng-provider/gradlew.bat @@ -9,7 +9,10 @@ if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8005,server=y,suspend=n + +set CATALINA_OPTS=-Xms512m -Xmx512m +set JAVA_OPTS=-Xms512m -Xmx512m -XX:PermSize=256m -XX:MaxPermSize=512m set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 71d8e8430..52ebbd3cb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -143,7 +143,7 @@ public class CommandWrapper { public String getTransactionId() { return this.transactionId; } - + public Long getTemplateId() { return this.templateId; } @@ -230,6 +230,10 @@ public class CommandWrapper { return this.entityName.equalsIgnoreCase("CURRENCY"); } + public boolean isSmsResource() { + return this.entityName.equalsIgnoreCase("SMS"); + } + public boolean isCodeResource() { return this.entityName.equalsIgnoreCase("CODE"); } @@ -265,7 +269,7 @@ public class CommandWrapper { public boolean isUpdateRunningbalance() { return this.actionName.equalsIgnoreCase("UPDATERUNNINGBALANCE") && this.entityName.equalsIgnoreCase("JOURNALENTRY"); } - + public boolean isFundResource() { return this.entityName.equalsIgnoreCase("FUND"); } @@ -329,7 +333,7 @@ public class CommandWrapper { public boolean isCollateralResource() { return this.entityName.equalsIgnoreCase("COLLATERAL"); } - + public boolean isTemplateRessource() { return this.entityName.equalsIgnoreCase("Template"); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index af04545fd..6af616141 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -238,6 +238,30 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder createSms() { + this.actionName = "CREATE"; + this.entityName = "SMS"; + this.entityId = null; + this.href = "/sms/template"; + return this; + } + + public CommandWrapperBuilder updateSms(final Long resourceId) { + this.actionName = "UPDATE"; + this.entityName = "SMS"; + this.entityId = resourceId; + this.href = "/sms/" + resourceId; + return this; + } + + public CommandWrapperBuilder deleteSms(final Long resourceId) { + this.actionName = "DELETE"; + this.entityName = "SMS"; + this.entityId = resourceId; + this.href = "/sms/" + resourceId; + return this; + } + public CommandWrapperBuilder createCode() { this.actionName = "CREATE"; this.entityName = "CODE"; @@ -362,7 +386,7 @@ public class CommandWrapperBuilder { this.href = "/clientId/" + clientId + "?command=proposeTransfer"; return this; } - + public CommandWrapperBuilder proposeAndAcceptClientTransfer(final Long clientId) { this.actionName = "PROPOSEANDACCEPTTRANSFER"; this.entityName = "CLIENT"; @@ -773,7 +797,7 @@ public class CommandWrapperBuilder { this.href = "/journalentries/" + transactionId; return this; } - + public CommandWrapperBuilder updateRunningBalanceForJournalEntry() { this.actionName = "UPDATERUNNINGBALANCE"; this.entityName = "JOURNALENTRY"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index fd9b22a94..1d37d1264 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -254,6 +254,16 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } + } else if (wrapper.isSmsResource()) { + if (wrapper.isCreate()) { + handler = this.applicationContext.getBean("createSmsCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdate()) { + handler = this.applicationContext.getBean("updateSmsCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDelete()) { + handler = this.applicationContext.getBean("deleteSmsCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } } else if (wrapper.isCurrencyResource()) { handler = this.applicationContext.getBean("updateCurrencyCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isFundResource()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/ParameterListInclusionStrategy.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/ParameterListInclusionStrategy.java new file mode 100644 index 000000000..7e617eb9f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/ParameterListInclusionStrategy.java @@ -0,0 +1,31 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.core.api; + +import java.util.Set; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +public class ParameterListInclusionStrategy implements ExclusionStrategy { + + private final Set parameterNamesToInclude; + + public ParameterListInclusionStrategy(final Set parameterNamesToSkip) { + this.parameterNamesToInclude = parameterNamesToSkip; + } + + @Override + public boolean shouldSkipField(final FieldAttributes f) { + return !this.parameterNamesToInclude.contains(f.getName()); + } + + @SuppressWarnings("unused") + @Override + public boolean shouldSkipClass(final Class clazz) { + return false; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DefaultToApiJsonSerializer.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DefaultToApiJsonSerializer.java index 23bc6b38a..a24a0240e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DefaultToApiJsonSerializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DefaultToApiJsonSerializer.java @@ -80,6 +80,24 @@ public final class DefaultToApiJsonSerializer implements ToApiJsonSerializer< return serializeWithSettings(delegatedSerializer, settings, singleObject); } + @Override + public String serialize(final ApiRequestJsonSerializationSettings settings, final Collection collection) { + final Gson delegatedSerializer = findAppropriateSerializer(settings); + return serializeWithSettings(delegatedSerializer, settings, collection.toArray()); + } + + @Override + public String serialize(final ApiRequestJsonSerializationSettings settings, final T singleObject) { + final Gson delegatedSerializer = findAppropriateSerializer(settings); + return serializeWithSettings(delegatedSerializer, settings, singleObject); + } + + @Override + public String serialize(final ApiRequestJsonSerializationSettings settings, final Page singleObject) { + final Gson delegatedSerializer = findAppropriateSerializer(settings); + return serializeWithSettings(delegatedSerializer, settings, singleObject); + } + private String serializeWithSettings(final Gson gson, final ApiRequestJsonSerializationSettings settings, final Object[] dataObject) { String json = null; if (gson != null) { @@ -116,4 +134,13 @@ public final class DefaultToApiJsonSerializer implements ToApiJsonSerializer< } return gson; } + + private Gson findAppropriateSerializer(final ApiRequestJsonSerializationSettings settings) { + Gson gson = null; + if (settings.isPartialResponseRequired()) { + gson = this.helper.createGsonBuilderForPartialResponseFiltering(settings.isPrettyPrint(), + settings.getParametersForPartialResponse()); + } + return gson; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java index 13794f0cc..fc365de9a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/GoogleGsonSerializerHelper.java @@ -16,6 +16,7 @@ import org.mifosplatform.infrastructure.core.api.JodaDateTimeAdapter; import org.mifosplatform.infrastructure.core.api.JodaLocalDateAdapter; import org.mifosplatform.infrastructure.core.api.JodaMonthDayAdapter; import org.mifosplatform.infrastructure.core.api.ParameterListExclusionStrategy; +import org.mifosplatform.infrastructure.core.api.ParameterListInclusionStrategy; import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException; import org.springframework.stereotype.Service; @@ -40,6 +41,20 @@ public final class GoogleGsonSerializerHelper { return builder.create(); } + public Gson createGsonBuilderForPartialResponseFiltering(final boolean prettyPrint, final Set responseParameters) { + + final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters); + + final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy); + builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter()); + builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter()); + builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); + if (prettyPrint) { + builder.setPrettyPrinting(); + } + return builder.create(); + } + public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set supportedParameters, final boolean prettyPrint, final Set responseParameters) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ToApiJsonSerializer.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ToApiJsonSerializer.java index b51f047b6..2d7be25c5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ToApiJsonSerializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/ToApiJsonSerializer.java @@ -18,6 +18,17 @@ public interface ToApiJsonSerializer { String serializeResult(Object object); + String serialize(ApiRequestJsonSerializationSettings settings, Collection collection); + + String serialize(ApiRequestJsonSerializationSettings settings, T single); + + String serialize(ApiRequestJsonSerializationSettings settings, Page singleObject); + + // TODO: TECHDEBT - bottom three will be deprecated going forward to remove + // need for people to pass full list of supported parameters. It was only + // used in cases where the partial response features was used (fields=x,y,x) + // to report error if incorrect field was passed. From now on it will just + // ignore unknown fields and fail silently String serialize(ApiRequestJsonSerializationSettings settings, Collection collection, Set supportedResponseParameters); String serialize(ApiRequestJsonSerializationSettings settings, T single, Set supportedResponseParameters); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/SmsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/SmsApiConstants.java new file mode 100644 index 000000000..d4fc02e90 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/SmsApiConstants.java @@ -0,0 +1,35 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class SmsApiConstants { + + public static final String RESOURCE_NAME = "sms"; + + // general + public static final String localeParamName = "locale"; + public static final String dateFormatParamName = "dateFormat"; + + // request parameters + public static final String idParamName = "id"; + public static final String groupIdParamName = "groupId"; + public static final String clientIdParamName = "clientId"; + public static final String staffIdParamName = "staffId"; + public static final String messageParamName = "message"; + + // response parameters + public static final String statusParamName = "status"; + + public static final Set CREATE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, + dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName)); + + public static final Set UPDATE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(messageParamName)); + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/api/SmsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/api/SmsApiResource.java new file mode 100644 index 000000000..6e74ec126 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/api/SmsApiResource.java @@ -0,0 +1,114 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.api; + +import java.util.Collection; + +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.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.service.CommandWrapperBuilder; +import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.infrastructure.sms.data.SmsData; +import org.mifosplatform.infrastructure.sms.service.SmsReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/sms") +@Consumes({ MediaType.APPLICATION_JSON }) +@Produces({ MediaType.APPLICATION_JSON }) +@Component +@Scope("singleton") +public class SmsApiResource { + + private final String resourceNameForPermissions = "SMS"; + + private final PlatformSecurityContext context; + private final SmsReadPlatformService readPlatformService; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + + @Autowired + public SmsApiResource(final PlatformSecurityContext context, final SmsReadPlatformService readPlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.context = context; + this.readPlatformService = readPlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + @GET + public String retrieveAll(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final Collection smsMessages = this.readPlatformService.retrieveAll(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, smsMessages); + } + + @POST + public String create(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createSms().withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{resourceId}") + public String retrieveOne(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) { + + final SmsData smsMessage = this.readPlatformService.retrieveOne(resourceId); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, smsMessage); + } + + @PUT + @Path("{resourceId}") + public String update(@PathParam("resourceId") final Long resourceId, final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSms(resourceId).withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @DELETE + @Path("{resourceId}") + public String delete(@PathParam("resourceId") final Long resourceId) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSms(resourceId).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/data/SmsData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/data/SmsData.java new file mode 100644 index 000000000..683b865c6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/data/SmsData.java @@ -0,0 +1,45 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.data; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; + +/** + * Immutable data object representing a SMS message. + */ +public class SmsData { + + @SuppressWarnings("unused") + private final Long id; + @SuppressWarnings("unused") + private final Long groupId; + @SuppressWarnings("unused") + private final Long clientId; + @SuppressWarnings("unused") + private final Long staffId; + @SuppressWarnings("unused") + private final EnumOptionData status; + @SuppressWarnings("unused") + private final String mobileNo; + @SuppressWarnings("unused") + private final String message; + + public static SmsData instance(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status, + final String mobileNo, final String message) { + return new SmsData(id, groupId, clientId, staffId, status, mobileNo, message); + } + + private SmsData(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status, + final String mobileNo, final String message) { + this.id = id; + this.groupId = groupId; + this.clientId = clientId; + this.staffId = staffId; + this.status = status; + this.mobileNo = mobileNo; + this.message = message; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/data/SmsDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/data/SmsDataValidator.java new file mode 100644 index 000000000..889df8df7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/data/SmsDataValidator.java @@ -0,0 +1,124 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.data; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.sms.SmsApiConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +@Component +public final class SmsDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public SmsDataValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForCreate(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsApiConstants.CREATE_REQUEST_DATA_PARAMETERS); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsApiConstants.RESOURCE_NAME); + + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) { + final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element); + baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).value(groupId).notNull().integerGreaterThanZero(); + + // ensure clientId and staffId are not passed + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) { + baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).failWithCode("cannot.be.passed.with.groupId"); + } + + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) { + baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).failWithCode("cannot.be.passed.with.groupId"); + } + } else if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) { + final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element); + baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).value(clientId).notNull().integerGreaterThanZero(); + + // ensure groupId and staffId are not passed + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) { + baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).failWithCode("cannot.be.passed.with.clientId"); + } + + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) { + baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).failWithCode("cannot.be.passed.with.clientId"); + } + } else if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) { + final Long staffId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.staffIdParamName, element); + baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).value(staffId).ignoreIfNull().longGreaterThanZero(); + + // ensure groupId and clientId are not passed + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) { + baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).failWithCode("cannot.be.passed.with.staffId"); + } + + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) { + baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).failWithCode("cannot.be.passed.with.staffId"); + } + } + + if (!this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element) + && !this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element) + && !this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) { + baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName) + .failWithCodeNoParameterAddedToErrorCode("no.entity.provided"); + } + + final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element); + baseDataValidator.reset().parameter(SmsApiConstants.messageParamName).value(message).notBlank().notExceedingLengthOf(1000); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForUpdate(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsApiConstants.UPDATE_REQUEST_DATA_PARAMETERS); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsApiConstants.RESOURCE_NAME); + + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.messageParamName, element)) { + final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element); + baseDataValidator.reset().parameter(SmsApiConstants.messageParamName).value(message).notBlank().notExceedingLengthOf(1000); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessage.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessage.java new file mode 100644 index 000000000..e33a49f88 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessage.java @@ -0,0 +1,81 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.domain; + +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.apache.commons.lang3.StringUtils; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.sms.SmsApiConstants; +import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.group.domain.Group; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "sms_messages_outbound") +public class SmsMessage extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "group_id", nullable = true) + private Group group; + + @ManyToOne + @JoinColumn(name = "client_id", nullable = true) + private Client client; + + @ManyToOne + @JoinColumn(name = "staff_id", nullable = true) + private Staff staff; + + @Column(name = "status_enum", nullable = false) + private Integer statusType; + + @Column(name = "mobile_no", nullable = false) + private String mobileNo; + + @Column(name = "message", nullable = false) + private String message; + + public static SmsMessage pendingSms(final Group group, final Client client, final Staff staff, final String message, + final String mobileNo) { + return new SmsMessage(group, client, staff, SmsMessageStatusType.PENDING, message, mobileNo); + } + + protected SmsMessage() { + // + } + + private SmsMessage(final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType, + final String message, final String mobileNo) { + this.group = group; + this.client = client; + this.staff = staff; + this.statusType = statusType.getValue(); + this.mobileNo = mobileNo; + this.message = message; + } + + public Map update(final JsonCommand command) { + + final Map actualChanges = new LinkedHashMap(1); + + if (command.isChangeInStringParameterNamed(SmsApiConstants.messageParamName, this.message)) { + final String newValue = command.stringValueOfParameterNamed(SmsApiConstants.messageParamName); + actualChanges.put(SmsApiConstants.messageParamName, newValue); + this.message = StringUtils.defaultIfEmpty(newValue, null); + } + + return actualChanges; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageAssembler.java new file mode 100644 index 000000000..e3bbf09a8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageAssembler.java @@ -0,0 +1,82 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.domain; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.sms.SmsApiConstants; +import org.mifosplatform.infrastructure.sms.exception.SmsNotFoundException; +import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper; +import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; +import org.mifosplatform.portfolio.group.domain.Group; +import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; + +@Component +public class SmsMessageAssembler { + + private final SmsMessageRepository smsMessageRepository; + private final GroupRepositoryWrapper groupRepository; + private final ClientRepositoryWrapper clientRepository; + private final StaffRepositoryWrapper staffRepository; + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public SmsMessageAssembler(final SmsMessageRepository smsMessageRepository, + final GroupRepositoryWrapper groupRepositoryWrapper, + final ClientRepositoryWrapper clientRepository, + final StaffRepositoryWrapper staffRepository, + final FromJsonHelper fromApiJsonHelper) { + this.smsMessageRepository = smsMessageRepository; + this.groupRepository = groupRepositoryWrapper; + this.clientRepository = clientRepository; + this.staffRepository = staffRepository; + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public SmsMessage assembleFromJson(final JsonCommand command) { + + final JsonElement element = command.parsedJson(); + + String mobileNo = null; + + Group group = null; + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) { + final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element); + group = this.groupRepository.findOneWithNotFoundDetection(groupId); + mobileNo = "+353 87 3455667"; + } + + Client client = null; + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) { + final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element); + client = this.clientRepository.findOneWithNotFoundDetection(clientId); + mobileNo = "+353 87 3455667"; // get from entity + } + + Staff staff = null; + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) { + final Long staffId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.staffIdParamName, element); + staff = this.staffRepository.findOneWithNotFoundDetection(staffId); + mobileNo = "+353 87 3455667"; // get from entity + } + + final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element); + + return SmsMessage.pendingSms(group, client, staff, message, mobileNo); + } + + public SmsMessage assembleFromResourceId(final Long resourceId) { + final SmsMessage sms = this.smsMessageRepository.findOne(resourceId); + if (sms == null) { throw new SmsNotFoundException(resourceId); } + return sms; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageEnumerations.java new file mode 100644 index 000000000..b514bb64f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageEnumerations.java @@ -0,0 +1,40 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.domain; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; + +public class SmsMessageEnumerations { + + public static EnumOptionData status(final Integer statusId) { + return status(SmsMessageStatusType.fromInt(statusId)); + } + + public static EnumOptionData status(final SmsMessageStatusType status) { + EnumOptionData optionData = new EnumOptionData(SmsMessageStatusType.INVALID.getValue().longValue(), SmsMessageStatusType.INVALID.getCode(), + "Invalid"); + switch (status) { + case INVALID: + optionData = new EnumOptionData(SmsMessageStatusType.INVALID.getValue().longValue(), SmsMessageStatusType.INVALID.getCode(), "Invalid"); + break; + case PENDING: + optionData = new EnumOptionData(SmsMessageStatusType.PENDING.getValue().longValue(), SmsMessageStatusType.PENDING.getCode(), "Pending"); + break; + case SENT: + optionData = new EnumOptionData(SmsMessageStatusType.SENT.getValue().longValue(), SmsMessageStatusType.SENT.getCode(), "Sent"); + break; + case DELIVERED: + optionData = new EnumOptionData(SmsMessageStatusType.DELIVERED.getValue().longValue(), SmsMessageStatusType.DELIVERED.getCode(), "Delivered"); + break; + case FAILED: + optionData = new EnumOptionData(SmsMessageStatusType.FAILED.getValue().longValue(), SmsMessageStatusType.FAILED.getCode(), "Failed"); + break; + + } + + return optionData; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageRepository.java new file mode 100644 index 000000000..4e45631b1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageRepository.java @@ -0,0 +1,13 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface SmsMessageRepository extends JpaRepository, JpaSpecificationExecutor { + // no extra behaviour +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageStatusType.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageStatusType.java new file mode 100644 index 000000000..54726230f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/domain/SmsMessageStatusType.java @@ -0,0 +1,46 @@ +package org.mifosplatform.infrastructure.sms.domain; + +public enum SmsMessageStatusType { + + INVALID(0, "smsMessageStatusType.invalid"), // + PENDING(100, "smsMessageStatusType.pending"), // + SENT(200, "smsMessageStatusType.sent"), // + DELIVERED(300, "smsMessageStatusType.delivered"), // + FAILED(400, "smsMessageStatusType.failed"); + + private final Integer value; + private final String code; + + public static SmsMessageStatusType fromInt(final Integer statusValue) { + + SmsMessageStatusType enumeration = SmsMessageStatusType.INVALID; + switch (statusValue) { + case 100: + enumeration = SmsMessageStatusType.PENDING; + break; + case 200: + enumeration = SmsMessageStatusType.SENT; + break; + case 300: + enumeration = SmsMessageStatusType.DELIVERED; + break; + case 400: + enumeration = SmsMessageStatusType.FAILED; + break; + } + return enumeration; + } + + private SmsMessageStatusType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/exception/SmsNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/exception/SmsNotFoundException.java new file mode 100644 index 000000000..1fb8c948a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/exception/SmsNotFoundException.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +/** + * A {@link RuntimeException} thrown when a code is not found. + */ +public class SmsNotFoundException extends AbstractPlatformResourceNotFoundException { + + public SmsNotFoundException(final Long resourceId) { + super("error.msg.sms.identifier.not.found", "SMS with identifier `" + resourceId + "` does not exist", resourceId); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/CreateSmsCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/CreateSmsCommandHandler.java new file mode 100644 index 000000000..3987752ad --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/CreateSmsCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.sms.service.SmsWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CreateSmsCommandHandler implements NewCommandSourceHandler { + + private final SmsWritePlatformService writePlatformService; + + @Autowired + public CreateSmsCommandHandler(final SmsWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.create(command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/DeleteSmsCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/DeleteSmsCommandHandler.java new file mode 100644 index 000000000..cad18ebc8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/DeleteSmsCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.sms.service.SmsWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class DeleteSmsCommandHandler implements NewCommandSourceHandler { + + private final SmsWritePlatformService writePlatformService; + + @Autowired + public DeleteSmsCommandHandler(final SmsWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.delete(command.entityId()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/UpdateSmsCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/UpdateSmsCommandHandler.java new file mode 100644 index 000000000..6ca829b8f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/handler/UpdateSmsCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.sms.service.SmsWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UpdateSmsCommandHandler implements NewCommandSourceHandler { + + private final SmsWritePlatformService writePlatformService; + + @Autowired + public UpdateSmsCommandHandler(final SmsWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.update(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsReadPlatformService.java new file mode 100644 index 000000000..b7682a0df --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsReadPlatformService.java @@ -0,0 +1,17 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.service; + +import java.util.Collection; + +import org.mifosplatform.infrastructure.sms.data.SmsData; + +public interface SmsReadPlatformService { + + Collection retrieveAll(); + + SmsData retrieveOne(Long resourceId); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsReadPlatformServiceImpl.java new file mode 100644 index 000000000..76511e841 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsReadPlatformServiceImpl.java @@ -0,0 +1,94 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.infrastructure.sms.data.SmsData; +import org.mifosplatform.infrastructure.sms.domain.SmsMessageEnumerations; +import org.mifosplatform.infrastructure.sms.exception.SmsNotFoundException; +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 SmsReadPlatformServiceImpl implements SmsReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + private final SmsMapper smsRowMapper; + + @Autowired + public SmsReadPlatformServiceImpl(final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.smsRowMapper = new SmsMapper(); + } + + private static final class SmsMapper implements RowMapper { + + final String schema; + + public SmsMapper() { + final StringBuilder sql = new StringBuilder(300); + sql.append("smo.id as id, "); + sql.append("smo.group_id as groupId, "); + sql.append("smo.client_id as clientId, "); + sql.append("smo.staff_id as staffId, "); + sql.append("smo.status_enum as statusId, "); + sql.append("smo.mobile_no as mobileNo, "); + sql.append("smo.message as message "); + sql.append("from sms_messages_outbound smo"); + + this.schema = sql.toString(); + } + + public String schema() { + return this.schema; + } + + @Override + public SmsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = JdbcSupport.getLong(rs, "id"); + final Long groupId = JdbcSupport.getLong(rs, "groupId"); + final Long clientId = JdbcSupport.getLong(rs, "clientId"); + final Long staffId = JdbcSupport.getLong(rs, "staffId"); + + final String mobileNo = rs.getString("mobileNo"); + final String message = rs.getString("message"); + + final Integer statusId = JdbcSupport.getInteger(rs, "statusId"); + final EnumOptionData status = SmsMessageEnumerations.status(statusId); + + return SmsData.instance(id, groupId, clientId, staffId, status, mobileNo, message); + } + } + + @Override + public Collection retrieveAll() { + + final String sql = "select " + this.smsRowMapper.schema(); + + return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {}); + } + + @Override + public SmsData retrieveOne(final Long resourceId) { + try { + final String sql = "select " + this.smsRowMapper.schema() + " where smo.id = ?"; + + return this.jdbcTemplate.queryForObject(sql, this.smsRowMapper, new Object[] { resourceId }); + } catch (final EmptyResultDataAccessException e) { + throw new SmsNotFoundException(resourceId); + } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsWritePlatformService.java new file mode 100644 index 000000000..2baf2f7dc --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsWritePlatformService.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface SmsWritePlatformService { + + CommandProcessingResult create(JsonCommand command); + + CommandProcessingResult update(Long resourceId, JsonCommand command); + + CommandProcessingResult delete(Long resourceId); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..a6f4d3f14 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/sms/service/SmsWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,119 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.sms.service; + +import java.util.Map; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.sms.data.SmsDataValidator; +import org.mifosplatform.infrastructure.sms.domain.SmsMessage; +import org.mifosplatform.infrastructure.sms.domain.SmsMessageAssembler; +import org.mifosplatform.infrastructure.sms.domain.SmsMessageRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SmsWritePlatformServiceJpaRepositoryImpl implements SmsWritePlatformService { + + private final static Logger logger = LoggerFactory.getLogger(SmsWritePlatformServiceJpaRepositoryImpl.class); + + private final SmsMessageAssembler assembler; + private final SmsMessageRepository repository; + private final SmsDataValidator validator; + + @Autowired + public SmsWritePlatformServiceJpaRepositoryImpl(final SmsMessageAssembler assembler, final SmsMessageRepository repository, + final SmsDataValidator validator) { + this.assembler = assembler; + this.repository = repository; + this.validator = validator; + } + + @Transactional + @Override + public CommandProcessingResult create(final JsonCommand command) { + + try { + this.validator.validateForCreate(command.json()); + + final SmsMessage message = this.assembler.assembleFromJson(command); + + // TODO: at this point we also want to fire off request using third party service to send SMS. + // TODO: decision to be made on wheter we 'wait' for response or use 'future/promise' to capture response and update the SmsMessage table + this.repository.save(message); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(message.getId()) // + .build(); + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + } + + @Transactional + @Override + public CommandProcessingResult update(final Long resourceId, final JsonCommand command) { + + try { + this.validator.validateForUpdate(command.json()); + + final SmsMessage message = this.assembler.assembleFromResourceId(resourceId); + final Map changes = message.update(command); + if (!changes.isEmpty()) { + this.repository.save(message); + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(resourceId) // + .with(changes) // + .build(); + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + } + + @Transactional + @Override + public CommandProcessingResult delete(final Long resourceId) { + + try { + final SmsMessage message = this.assembler.assembleFromResourceId(resourceId); + this.repository.delete(message); + this.repository.flush(); + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(null, dve); + return CommandProcessingResult.empty(); + } + return new CommandProcessingResultBuilder().withEntityId(resourceId).build(); + } + + /* + * Guaranteed to throw an exception no matter what the data integrity issue + * is. + */ + private void handleDataIntegrityIssues(@SuppressWarnings("unused") final JsonCommand command, final DataIntegrityViolationException dve) { + final Throwable realCause = dve.getMostSpecificCause(); + + if (realCause.getMessage().contains("mobile_no")) { + throw new PlatformDataIntegrityException("error.msg.sms.no.mobile.no.exists", "The group, client or staff provided has no mobile no.", "id"); + } + + logger.error(dve.getMessage(), dve); + throw new PlatformDataIntegrityException("error.msg.sms.unknown.data.integrity.issue", + "Unknown data integrity issue with resource: " + realCause.getMessage()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java index d89afcec5..c615ae627 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java @@ -65,10 +65,9 @@ public class LoanCharge extends AbstractPersistable { @Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true) private BigDecimal amountPercentageAppliedTo; - @SuppressWarnings("unused") @Column(name = "charge_amount_or_percentage", scale = 6, precision = 19, nullable = false) private BigDecimal amountOrPercentage; - + @Column(name = "amount", scale = 6, precision = 19, nullable = false) private BigDecimal amount; @@ -121,7 +120,7 @@ public class LoanCharge extends AbstractPersistable { default: break; } - + BigDecimal loanCharge = BigDecimal.ZERO; if(ChargeTimeType.fromInt(chargeDefinition.getChargeTime()).equals(ChargeTimeType.INSTALMENT_FEE)){ BigDecimal percentage = amount; @@ -134,7 +133,7 @@ public class LoanCharge extends AbstractPersistable { return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode, null, loanCharge); } - + /* * loanPrincipal is required for charges that are percentage based */ @@ -150,7 +149,7 @@ public class LoanCharge extends AbstractPersistable { public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, - final ChargePaymentMode chargePaymentMode, Integer numberOfRepayments, BigDecimal loanCharge) { + final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final BigDecimal loanCharge) { this.loan = loan; this.charge = chargeDefinition; this.penaltyCharge = chargeDefinition.isPenalty(); @@ -209,7 +208,7 @@ public class LoanCharge extends AbstractPersistable { this.percentage = null; this.amountPercentageAppliedTo = null; this.amountPaid = null; - if(this.isInstalmentFee()){ + if(isInstalmentFee()){ if(numberOfRepayments == null){ numberOfRepayments = this.loan.repaymentScheduleDetail().getNumberOfRepayments(); } @@ -286,7 +285,7 @@ public class LoanCharge extends AbstractPersistable { case INVALID: break; case FLAT: - if(this.isInstalmentFee()){ + if(isInstalmentFee()){ if(numberOfRepayments == null){ numberOfRepayments = this.loan.repaymentScheduleDetail().getNumberOfRepayments(); } @@ -338,7 +337,7 @@ public class LoanCharge extends AbstractPersistable { case INVALID: break; case FLAT: - if(this.isInstalmentFee()){ + if(isInstalmentFee()){ this.amount = newValue.multiply(BigDecimal.valueOf(this.loan.repaymentScheduleDetail().getNumberOfRepayments())); }else{ this.amount = newValue; @@ -349,7 +348,7 @@ public class LoanCharge extends AbstractPersistable { case PERCENT_OF_INTEREST: this.percentage = newValue; this.amountPercentageAppliedTo = amount; - BigDecimal loanCharge = loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(this.chargeCalculation),percentage); + BigDecimal loanCharge = this.loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(this.chargeCalculation),this.percentage); if(loanCharge.compareTo(BigDecimal.ZERO) == 0){ loanCharge = percentageOf(this.amountPercentageAppliedTo); } @@ -369,7 +368,7 @@ public class LoanCharge extends AbstractPersistable { public boolean isSpecifiedDueDate() { return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE); } - + public boolean isInstalmentFee() { return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.INSTALMENT_FEE); } @@ -424,8 +423,8 @@ public class LoanCharge extends AbstractPersistable { public BigDecimal percentageOf(final BigDecimal value) { return percentageOf(value, this.percentage); } - - public static BigDecimal percentageOf(final BigDecimal value,BigDecimal percentage) { + + public static BigDecimal percentageOf(final BigDecimal value,final BigDecimal percentage) { BigDecimal percentageOf = BigDecimal.ZERO; @@ -442,17 +441,17 @@ public class LoanCharge extends AbstractPersistable { * @returns a minimum cap or maximum cap set on charges if the criteria fits * else it returns the percentageOf if the amount is within min and max cap */ - private BigDecimal minimumAndMaximumCap(BigDecimal percentageOf){ + private BigDecimal minimumAndMaximumCap(final BigDecimal percentageOf){ BigDecimal minMaxCap = BigDecimal.ZERO; if(this.minCap !=null){ - int minimumCap = percentageOf.compareTo(minCap); + final int minimumCap = percentageOf.compareTo(this.minCap); if(minimumCap == -1){ minMaxCap = this.minCap; return minMaxCap; } } if(this.maxCap !=null){ - int maximumCap = percentageOf.compareTo(maxCap); + final int maximumCap = percentageOf.compareTo(this.maxCap); if(maximumCap == 1) { minMaxCap = this.maxCap; return minMaxCap; @@ -607,18 +606,18 @@ public class LoanCharge extends AbstractPersistable { return ChargePaymentMode.fromInt(this.chargePaymentMode); } - + public ChargeCalculationType getChargeCalculation() { return ChargeCalculationType.fromInt(this.chargeCalculation); } - + public BigDecimal getPercentage() { return this.percentage; } - - public void updateAmount(BigDecimal amount) { + + public void updateAmount(final BigDecimal amount) { this.amount = amount; calculateOutstanding(); }