diff --git a/.gitignore b/.gitignore index fe4f2dcb3..5aecc53c7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ rebel.xml *.DS_Store *.ini *~ +.metadata diff --git a/mifosng-db/migrations/core_db/V119__add_template_table.sql b/mifosng-db/migrations/core_db/V119__add_template_table.sql new file mode 100644 index 000000000..190af26ba --- /dev/null +++ b/mifosng-db/migrations/core_db/V119__add_template_table.sql @@ -0,0 +1,34 @@ +DROP TABLE IF EXISTS `m_template`; +CREATE TABLE `m_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `text` longtext COLLATE utf8_unicode_ci NOT NULL, + `entity` int(11) DEFAULT NULL, + `type` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unq_name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `m_templatemappers`; +CREATE TABLE `m_templatemappers` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `mapperkey` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `mapperorder` int(11) DEFAULT NULL, + `mappervalue` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `m_template_m_templatemappers`; +CREATE TABLE `m_template_m_templatemappers` ( + `m_template_id` bigint(20) NOT NULL, + `mappers_id` bigint(20) NOT NULL, + UNIQUE KEY `mappers_id` (`mappers_id`), + KEY (`mappers_id`), + KEY (`m_template_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('organistion', 'DELETE_TEMPLATE', 'TEMPLATE', 'DELETE', 0), + ('organistion', 'CREATE_TEMPLATE', 'TEMPLATE', 'CREATE', 0), + ('organistion', 'UPDATE_TEMPLATE', 'TEMPLATE', 'UPDATE', 0), + ('organistion', 'READ_TEMPLATE', 'TEMPLATE', 'READ', 0); \ No newline at end of file diff --git a/mifosng-provider/build.gradle b/mifosng-provider/build.gradle index 4741c00e3..4a2fb34d0 100644 --- a/mifosng-provider/build.gradle +++ b/mifosng-provider/build.gradle @@ -99,6 +99,7 @@ dependencies { [group: 'org.springframework', name: 'spring-core', version: springVersion], [group: 'org.springframework', name: 'spring-aspects', version: springVersion], [group: 'org.springframework', name: 'spring-web', version: springVersion], + [group: 'org.springframework', name: 'spring-test', version: springVersion], [group: 'org.springframework.security', name: 'spring-security-core', version: springSecurityVersion], [group: 'org.springframework.security', name: 'spring-security-web', version: springSecurityVersion], [group: 'org.springframework.security', name: 'spring-security-config', version: springSecurityVersion], @@ -162,8 +163,8 @@ dependencies { [group: 'com.googlecode.flyway', name: 'flyway-maven-plugin', version: '2.1.1'], [group: 'org.quartz-scheduler', name: 'quartz', version: '2.1.7'], [group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.2.1'], - - [group: 'net.sf.ehcache', name: 'ehcache', version: '2.7.2'] + [group: 'net.sf.ehcache', name: 'ehcache', version: '2.7.2'], + [group: 'com.github.spullara.mustache.java', name: 'compiler', version: '0.8.12'] ) testCompile 'junit:junit:4.10', diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/TemplateIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/TemplateIntegrationTest.java new file mode 100644 index 000000000..260868eeb --- /dev/null +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/TemplateIntegrationTest.java @@ -0,0 +1,64 @@ +package org.mifosplatform.integrationtests; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mifosplatform.integrationtests.common.Utils; + +import com.google.gson.Gson; +import com.jayway.restassured.builder.RequestSpecBuilder; +import com.jayway.restassured.builder.ResponseSpecBuilder; +import com.jayway.restassured.http.ContentType; +import com.jayway.restassured.specification.RequestSpecification; +import com.jayway.restassured.specification.ResponseSpecification; + +public class TemplateIntegrationTest { + + private final String GET_TEMPLATES_URL = "/mifosng-provider/api/v1/templates?tenantIdentifier=default"; + private final String GET_TEMPLATE_ID_URL = "/mifosng-provider/api/v1/templates/%s?tenantIdentifier=default"; + private final String RESPONSE_ATTRIBUTE_NAME = "name"; + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + + @Before + public void setup() { + + Utils.initializeRESTAssured(); + requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + } + + @Test + public void test() { + + HashMap metadata = new HashMap(); + metadata.put("user", "resource_url"); + HashMap map = new HashMap(); + map.put("name", "foo"); + map.put("text", "Hello {{template}}"); + map.put("mappers", metadata); + + ArrayList get = Utils.performServerGet(requestSpec, responseSpec, GET_TEMPLATES_URL, ""); + int entriesBeforeTest = get.size(); + + Integer id = Utils.performServerPost(requestSpec, responseSpec, GET_TEMPLATES_URL, new Gson().toJson(map), "resourceId"); + + String templateUrlForId = String.format(GET_TEMPLATE_ID_URL, id); + + String getrequest2 = Utils.performServerGet(requestSpec, responseSpec, templateUrlForId, RESPONSE_ATTRIBUTE_NAME); + + Assert.assertTrue(getrequest2.equals("foo")); + + Utils.performServerDelete(requestSpec, responseSpec, templateUrlForId, ""); + + get = Utils.performServerGet(requestSpec, responseSpec, GET_TEMPLATES_URL, ""); + int entriesAfterTest = get.size(); + + Assert.assertEquals(entriesBeforeTest, entriesAfterTest); + } +} 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 4975a18c5..9170b9284 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 @@ -26,6 +26,7 @@ public class CommandWrapper { private final String supportedEntityType; private final Long supportedEntityId; private final Long productId; + private Long templateId; public static CommandWrapper wrap(final String actionName, final String entityName, final Long resourceId, final Long subresourceId) { return new CommandWrapper(null, actionName, entityName, resourceId, subresourceId, null, null); @@ -56,13 +57,12 @@ public class CommandWrapper { this.json = null; this.transactionId = null; this.productId = productId; - } public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String actionName, final String entityName, final Long entityId, final Long subentityId, final Long codeId, final String supportedEntityType, final Long supportedEntityId, final String href, final String json, - final String transactionId, final Long productId) { + final String transactionId, final Long productId, final Long templateId) { this.commandId = null; this.officeId = officeId; this.groupId = groupId; @@ -81,6 +81,7 @@ public class CommandWrapper { this.json = json; this.transactionId = transactionId; this.productId = productId; + this.templateId = templateId; } public Long commandId() { @@ -142,6 +143,10 @@ public class CommandWrapper { public String getTransactionId() { return this.transactionId; } + + public Long getTemplateId() { + return this.templateId; + } public String getEntityName() { return this.entityName; @@ -320,6 +325,10 @@ public class CommandWrapper { public boolean isCollateralResource() { return this.entityName.equalsIgnoreCase("COLLATERAL"); } + + public boolean isTemplateRessource() { + return this.entityName.equalsIgnoreCase("Template"); + } public boolean isApproveLoanApplication() { return this.actionName.equalsIgnoreCase("APPROVE") && this.entityName.equalsIgnoreCase("LOAN"); 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 362259eb6..849826e59 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 @@ -26,11 +26,12 @@ public class CommandWrapperBuilder { private String supportedEntityType; private Long supportedEntityId; private Long productId; + private Long templateId; public CommandWrapper build() { return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName, this.entityId, this.subentityId, this.codeId, this.supportedEntityType, this.supportedEntityId, this.href, - this.json, this.transactionId, this.productId); + this.json, this.transactionId, this.productId, this.templateId); } public CommandWrapperBuilder withLoanId(final Long withLoanId) { @@ -1305,6 +1306,33 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder createTemplate() { + this.actionName = "CREATE"; + this.entityName = "TEMPLATE"; + this.entityId = null; + this.templateId = null; + this.href = "/templates"; + return this; + } + + public CommandWrapperBuilder updateTemplate(final Long templateId) { + this.actionName = "UPDATE"; + this.entityName = "TEMPLATE"; + this.entityId = templateId; + this.templateId = templateId; + this.href = "/templates/" + templateId; + return this; + } + + public CommandWrapperBuilder deleteTemplate(final Long templateId) { + this.actionName = "DELETE"; + this.entityName = "TEMPLATE"; + this.entityId = templateId; + this.templateId = templateId; + this.href = "/templates/" + templateId; + return this; + } + public CommandWrapperBuilder assignClientStaff(final Long clientId) { this.actionName = "ASSIGNSTAFF"; this.entityName = "CLIENT"; 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 70ad36a57..de8046ddb 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 @@ -144,7 +144,6 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer throw new UnsupportedCommandException(wrapper.commandName()); } } else if (wrapper.isClientIdentifierResource()) { - if (wrapper.isCreate()) { handler = this.applicationContext.getBean("createClientIdentifierCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isUpdate()) { @@ -501,15 +500,12 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer throw new UnsupportedCommandException(wrapper.commandName()); } } else if (wrapper.isCollectionSheetResource()) { - if (wrapper.isUpdate()) { handler = this.applicationContext.getBean("updateCollectionSheetCommandHandler", NewCommandSourceHandler.class); } else { throw new UnsupportedCommandException(wrapper.commandName()); } - } else if (wrapper.isReportResource()) { - if (wrapper.isCreate()) { handler = this.applicationContext.getBean("createReportCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isUpdate()) { @@ -519,9 +515,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } - } else if (wrapper.isAccountingRuleResource()) { - if (wrapper.isCreate()) { handler = this.applicationContext.getBean("createAccountingRuleCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isUpdate()) { @@ -531,7 +525,6 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } - } else if (wrapper.isXBRLMappingResource()) { if (wrapper.isUpdate()) { handler = this.applicationContext.getBean("updateTaxonomyMappingCommandHandler", NewCommandSourceHandler.class); @@ -539,7 +532,6 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer throw new UnsupportedCommandException(wrapper.commandName()); } } else if (wrapper.isHolidayResource()) { - if (wrapper.isCreate()) { handler = this.applicationContext.getBean("createHolidayCommandHandler", NewCommandSourceHandler.class); } @@ -577,6 +569,29 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } + } else if (wrapper.isTemplateRessource()) { + + if (wrapper.isCreate()) { + handler = this.applicationContext.getBean("createTemplateCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdate()) { + handler = this.applicationContext.getBean("updateTemplateCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDelete()) { + handler = this.applicationContext.getBean("deleteTemplateCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } + + } else if (wrapper.isTemplateRessource()) { + if (wrapper.isCreate()) { + handler = this.applicationContext.getBean("createTemplateCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isUpdate()) { + handler = this.applicationContext.getBean("updateTemplateCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isDelete()) { + handler = this.applicationContext.getBean("deleteTemplateCommandHandler", NewCommandSourceHandler.class); + } else { + throw new UnsupportedCommandException(wrapper.commandName()); + } + } else { throw new UnsupportedCommandException(wrapper.commandName()); @@ -584,4 +599,4 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer return handler; } -} \ No newline at end of file +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java index a76c15ad8..9bb0e2451 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java @@ -10,6 +10,7 @@ import java.math.BigDecimal; import java.util.Arrays; import java.util.Date; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.apache.commons.lang.ObjectUtils; @@ -23,6 +24,7 @@ import org.mifosplatform.infrastructure.security.service.PlatformPasswordEncoder import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; /** * Immutable representation of a command. @@ -314,6 +316,12 @@ public final class JsonCommand { return this.fromApiJsonHelper.extractStringNamed(parameterName, this.parsedCommand); } + public Map mapValueOfParameterNamed(final String json) { + final Type typeOfMap = new TypeToken>() {}.getType(); + final Map value = this.fromApiJsonHelper.extractDataMap(typeOfMap, json); + return value; + } + public boolean isChangeInBigDecimalParameterNamedDefaultingZeroToNull(final String parameterName, final BigDecimal existingValue) { boolean isChanged = false; if (parameterExists(parameterName)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/template/api/TemplatesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/template/api/TemplatesApiResource.java new file mode 100644 index 000000000..dff56babd --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/template/api/TemplatesApiResource.java @@ -0,0 +1,206 @@ +package org.mifosplatform.template.api; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +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.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; + +import org.codehaus.jackson.map.ObjectMapper; +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.template.data.TemplateData; +import org.mifosplatform.template.domain.Template; +import org.mifosplatform.template.domain.TemplateEntity; +import org.mifosplatform.template.domain.TemplateType; +import org.mifosplatform.template.service.TemplateDomainService; +import org.mifosplatform.template.service.TemplateMergeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/templates") +@Component +@Scope("singleton") +public class TemplatesApiResource { + + private final Set RESPONSE_TEMPLATES_DATA_PARAMETERS = new HashSet(Arrays.asList("id")); + private final Set RESPONSE_TEMPLATE_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "entities", "types", "template")); + private final String RESOURCEN_NAME_FOR_PERMISSION = "template"; + + private final PlatformSecurityContext context; + private final DefaultToApiJsonSerializer