From 16996b5e8c0d728dd842e51e69d126a4d01eabf8 Mon Sep 17 00:00:00 2001 From: Lech Rozanski Date: Tue, 4 Jun 2013 14:48:41 +0200 Subject: [PATCH] MIFOSX-365: Simplify 'Data Tables' functionality --- api-docs/apiLive.htm | 265 ++++++++++++++++-- ...tatableCommandFromApiJsonDeserializer.java | 129 ++++----- .../data/ResultsetColumnHeaderData.java | 5 +- .../ReadWriteNonCoreDataServiceImpl.java | 200 ++++++++----- 4 files changed, 454 insertions(+), 145 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 1d55f8b51..a6608b6cc 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -1063,11 +1063,19 @@ Data Table datatables - + Create Data Table List Data Tables + + + datatables/{datatable} + + Retrieve Data Table Details + Update Data Table + Delete Data Table + datatables/register/{datatable}/{apptable} @@ -11276,6 +11284,57 @@ No Request Body: +   +
+
+

Create Data Table

+

Create a new data table and registers it with the Mifos X Core application + table.

+
+
+ +POST https://DomainName/api/v1/datatables + + +POST https://DomainName/api/v1/datatables + +Content-Type: application/json +Request Body: +{ + "datatableName": "extra_client_details", + "apptableName": "m_client", + "columns": [ + { + "name": "Gender", + "type": "Dropdown", + "code": "Gender" + }, + { + "name": "Some Decimal", + "type": "Decimal", + "mandatory": true + }, + { + "name": "Birth Date", + "type": "Date" + }, + { + "name": "Question 2", + "type": "String", + "length": 100, + "mandatory": false + } + ] +} + + +{ + "resourceIdentifier": "extra_client_details" +} + +
+
  @@ -11303,25 +11362,191 @@ No Request Body: GET https://DomainName/api/v1/datatables - + [ - { - "applicationTableName": "m_client", - "registeredTableName": "Additional Client Fields Data" - }, - { - "applicationTableName": "m_client", - "registeredTableName": "Extra Family Details Data" - }, - { - "applicationTableName": "m_loan", - "registeredTableName": "Additional Loan Fields Data" - } -] + { + "applicationTableName": "m_client", + "registeredTableName": "extra_client_details", + "columnHeaderData": [ + { + "columnName": "client_id", + "columnType": "bigint", + "columnLength": 0, + "columnDisplayType": "INTEGER", + "isColumnNullable": false, + "isColumnPrimaryKey": true, + "columnValues": [] + }, + { + "columnName": "Gender_cd_Question", + "columnType": "int", + "columnLength": 0, + "columnDisplayType": "INTEGER", + "isColumnNullable": true, + "isColumnPrimaryKey": false, + "columnValues": [] + }, + { + "columnName": "Some Decimal", + "columnType": "decimal", + "columnLength": 0, + "columnDisplayType": "DECIMAL", + "isColumnNullable": true, + "isColumnPrimaryKey": false, + "columnValues": [] + }, + { + "columnName": "Birth Date", + "columnType": "date", + "columnLength": 0, + "columnDisplayType": "DATE", + "isColumnNullable": true, + "isColumnPrimaryKey": false, + "columnValues": [] + } + ] + } +] +   +
+
+

Retrieve Data Table Details

+

Lists a registered data table details and the Mifos X Core + application table they are registered to.

+
+
+ +GET https://DomainName/api/v1/datatables/{datatable} + + +{ + "applicationTableName": "m_client", + "registeredTableName": "extra_client_details", + "columnHeaderData": [ + { + "columnName": "client_id", + "columnType": "bigint", + "columnLength": 0, + "columnDisplayType": "INTEGER", + "isColumnNullable": false, + "isColumnPrimaryKey": true, + "columnValues": [] + }, + { + "columnName": "Gender_cd_Question", + "columnType": "int", + "columnLength": 0, + "columnDisplayType": "INTEGER", + "isColumnNullable": true, + "isColumnPrimaryKey": false, + "columnValues": [] + }, + { + "columnName": "Some Decimal", + "columnType": "decimal", + "columnLength": 0, + "columnDisplayType": "DECIMAL", + "isColumnNullable": true, + "isColumnPrimaryKey": false, + "columnValues": [] + }, + { + "columnName": "Birth Date", + "columnType": "date", + "columnLength": 0, + "columnDisplayType": "DATE", + "isColumnNullable": true, + "isColumnPrimaryKey": false, + "columnValues": [] + } + ] +} + +
+
+ +   +
+
+

Update Data Table

+

Modifies fields of a data table. If the apptableName parameter is passed, + data table is deregistered and registered with the new application table.

+
+
+ +PUT https://DomainName/api/v1/datatables/{datatables} + + +PUT https://DomainName/api/v1/datatables/extra_client_details + +Content-Type: application/json +Request Body: +{ + "apptableName": "m_client", + "columns": [ + { + "name": "Gender", + "type": "Dropdown", + "code": "Gender" + }, + { + "name": "Some Decimal", + "type": "Decimal", + "mandatory": true + }, + { + "name": "Birth Date", + "type": "Date" + }, + { + "name": "Question 2", + "type": "String", + "length": 100, + "mandatory": false + } + ] +} + + +{ + "resourceIdentifier": "extra_client_details" +} + +
+
+ +   +
+
+

Delete Data Table

+

Deletes a data table and unregisters it from the Mifos X Core + application table.

+
+
+ +DELETE https://DomainName/api/v1/datatables/{datatables} + + +DELETE https://DomainName/api/v1/datatables/extra_client_details + +Content-Type: application/json +No Request Body + + +{ + "resourceIdentifier": "extra_client_details" +} + +
+ +
 
@@ -15357,11 +15582,19 @@ GET https://DomainName/api/v1/search?query=000000111 Data Table datatables - + Create Data Table List Data Tables + + + datatables/{datatable} + + Retrieve Data Table Details + Update Data Table + Delete Data Table + datatables/register/{datatable}/{apptable} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java index db1f74a08..1a214ca61 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java @@ -18,6 +18,7 @@ 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.exception.PlatformDataIntegrityException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -29,22 +30,23 @@ import com.google.gson.reflect.TypeToken; public class DatatableCommandFromApiJsonDeserializer { private final static String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$"; + private final static String DATATABLE_COLUMN_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,}[a-zA-Z0-9]$"; /** * The parameters supported for this command. */ private final Set supportedParametersForCreate = new HashSet(Arrays.asList( "datatableName", "apptableName", "multiRow", "columns")); private final Set supportedParametersForCreateColumns = new HashSet(Arrays.asList( - "name", "type", "length", "mandatory")); + "name", "type", "length", "mandatory", "code")); private final Set supportedParametersForUpdate = new HashSet(Arrays.asList( "apptableName", "changeColumns", "addColumns", "dropColumns")); private final Set supportedParametersForAddColumns = new HashSet(Arrays.asList( - "name", "type", "length", "mandatory", "after")); + "name", "type", "length", "mandatory", "after", "code")); private final Set supportedParametersForChangeColumns = new HashSet(Arrays.asList( - "name", "newName", "type", "length", "mandatory", "after")); + "name", "newName", "mandatory", "after", "code", "newCode")); private final Set supportedParametersForDropColumns = new HashSet(Arrays.asList( "name")); - private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date", "Text" }; + private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date", "Text", "Dropdown" }; private final Object[] supportedApptableNames = { "m_loan", "m_savings_account", "m_client", "m_group", "m_office" }; private final FromJsonHelper fromApiJsonHelper; @@ -54,6 +56,35 @@ public class DatatableCommandFromApiJsonDeserializer { this.fromApiJsonHelper = fromApiJsonHelper; } + private void validateType(final DataValidatorBuilder baseDataValidator, final JsonElement column) { + final String type = fromApiJsonHelper.extractStringNamed("type", column); + baseDataValidator.reset().parameter("type").value(type).notBlank() + .isOneOfTheseValues(supportedColumnTypes); + + final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column); + if (type != null && type.equals("String")) { + if (length != null) { + baseDataValidator.reset().parameter("length").value(length).notBlank() + .zeroOrPositiveAmount(); + } else { + baseDataValidator.reset().parameter("length").value(length) + .cantBeBlankWhenParameterProvidedIs("type", type); + } + } else { + baseDataValidator.reset().parameter("length").value(length) + .mustBeBlankWhenParameterProvidedIs("type", type); + } + + final String code = fromApiJsonHelper.extractStringNamed("code", column); + + if (type != null && type.equals("Dropdown")) { + baseDataValidator.reset().parameter("code").value(code).notBlank() + .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + } else { + baseDataValidator.reset().parameter("code").value(code).mustBeBlankWhenParameterProvided("type", type); + } + } + public void validateForCreate(final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } @@ -83,28 +114,16 @@ public class DatatableCommandFromApiJsonDeserializer { if (columns != null) { for (JsonElement column : columns) { fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForCreateColumns); - + final String name = fromApiJsonHelper.extractStringNamed("name", column); - baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) - .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); - + baseDataValidator.reset().parameter("name").value(name).notBlank() + .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); + final String type = fromApiJsonHelper.extractStringNamed("type", column); baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseValues(supportedColumnTypes); - - final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column); - if (type != null && (type.equals("String") || type.equals("Text"))) { - if (length != null) { - baseDataValidator.reset().parameter("length").value(length).notBlank() - .zeroOrPositiveAmount(); - } else if (!type.equals("Text")) { - baseDataValidator.reset().parameter("length").value(length) - .cantBeBlankWhenParameterProvidedIs("type", type); - } - } else if (type != null && length != null) { - baseDataValidator.reset().parameter("length").value(length) - .mustBeBlankWhenParameterProvidedIs("type", type); - } - + + validateType(baseDataValidator, column); + final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column); baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank() .isOneOfTheseValues(true, false); @@ -118,7 +137,10 @@ public class DatatableCommandFromApiJsonDeserializer { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } // Because all parameters are optional, a check to see if at least one parameter // has been specified is necessary in order to avoid JSON requests with no parameters - if (!json.matches("(?s)\\A\\{.*?(\\\".*?\\\"\\s*?:\\s*?)+.*?\\}\\z")) { throw new InvalidJsonException(); } + if (!json.matches("(?s)\\A\\{.*?(\\\".*?\\\"\\s*?:\\s*?)+.*?\\}\\z")) { + throw new PlatformDataIntegrityException("error.msg.invalid.request.body.no.parameters", + "Provided JSON request body does not have any parameters."); + } final Type typeOfMap = new TypeToken>() {}.getType(); fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForUpdate); @@ -129,7 +151,7 @@ public class DatatableCommandFromApiJsonDeserializer { final JsonElement element = fromApiJsonHelper.parse(json); final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element); baseDataValidator.reset().parameter("apptableName").value(apptableName).ignoreIfNull().notBlank() - .notExceedingLengthOf(50).isOneOfTheseValues(supportedApptableNames); + .isOneOfTheseValues(supportedApptableNames); final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : ""; final JsonArray changeColumns = fromApiJsonHelper.extractJsonArrayNamed("changeColumns", element); @@ -140,36 +162,31 @@ public class DatatableCommandFromApiJsonDeserializer { fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForChangeColumns); final String name = fromApiJsonHelper.extractStringNamed("name", column); - baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) - .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + baseDataValidator.reset().parameter("name").value(name).notBlank() + .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); final String newName = fromApiJsonHelper.extractStringNamed("newName", column); baseDataValidator.reset().parameter("newName").value(newName).ignoreIfNull().notBlank() .notExceedingLengthOf(50).isNotOneOfTheseValues("id", fkColumnName) .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); - final String type = fromApiJsonHelper.extractStringNamed("type", column); - baseDataValidator.reset().parameter("type").value(type).notBlank() - .isOneOfTheseValues(supportedColumnTypes); + final String code = fromApiJsonHelper.extractStringNamed("code", column); + baseDataValidator.reset().parameter("code").value(code).ignoreIfNull().notBlank() + .notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); - final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column); - if (type != null && (type.equals("String") || type.equals("Text"))) { - if (length != null) { - baseDataValidator.reset().parameter("length").value(length).notBlank() - .zeroOrPositiveAmount(); - } else if (!type.equals("Text")) { - baseDataValidator.reset().parameter("length").value(length) - .cantBeBlankWhenParameterProvidedIs("type", type); - } - } else if (type != null && length != null) { - baseDataValidator.reset().parameter("length").value(length) - .mustBeBlankWhenParameterProvidedIs("type", type); + final String newCode = fromApiJsonHelper.extractStringNamed("newCode", column); + baseDataValidator.reset().parameter("newCode").value(newCode).ignoreIfNull().notBlank() + .notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); + + if (StringUtils.isBlank(code) && StringUtils.isNotBlank(newCode)) { + baseDataValidator.reset().parameter("code").value(code) + .cantBeBlankWhenParameterProvidedIs("newCode", newCode); } final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column); baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank() .isOneOfTheseValues(true, false); - + final Boolean after = fromApiJsonHelper.extractBooleanNamed("after", column); baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank() .isOneOfTheseValues(true, false); @@ -184,26 +201,10 @@ public class DatatableCommandFromApiJsonDeserializer { fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForAddColumns); final String name = fromApiJsonHelper.extractStringNamed("name", column); - baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) - .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + baseDataValidator.reset().parameter("name").value(name).notBlank() + .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); - final String type = fromApiJsonHelper.extractStringNamed("type", column); - baseDataValidator.reset().parameter("type").value(type).notBlank() - .isOneOfTheseValues(supportedColumnTypes); - - final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column); - if (type != null && (type.equals("String") || type.equals("Text"))) { - if (length != null) { - baseDataValidator.reset().parameter("length").value(length).notBlank() - .zeroOrPositiveAmount(); - } else if (!type.equals("Text")) { - baseDataValidator.reset().parameter("length").value(length) - .cantBeBlankWhenParameterProvidedIs("type", type); - } - } else if (type != null && length != null) { - baseDataValidator.reset().parameter("length").value(length) - .mustBeBlankWhenParameterProvidedIs("type", type); - } + validateType(baseDataValidator, column); final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column); baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank() @@ -223,8 +224,8 @@ public class DatatableCommandFromApiJsonDeserializer { fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForDropColumns); final String name = fromApiJsonHelper.extractStringNamed("name", column); - baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) - .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + baseDataValidator.reset().parameter("name").value(name).notBlank() + .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java index fd394c644..c13e69d3c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java @@ -17,7 +17,6 @@ public final class ResultsetColumnHeaderData { private final String columnName; private final String columnType; - @SuppressWarnings("unused") private final Long columnLength; private final String columnDisplayType; private final boolean isColumnNullable; @@ -161,6 +160,10 @@ public final class ResultsetColumnHeaderData { public String getColumnType() { return this.columnType; } + + public Long getColumnLength() { + return this.columnLength; + } public String getColumnDisplayType() { return this.columnDisplayType; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index 4b6ebd737..a46f1b5bf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -62,7 +62,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class); private final static HashMap apiTypeToMySQL = new HashMap() {{ put("String", "VARCHAR"); put("Number", "INT"); put("Decimal", "DECIMAL"); put("Date", "DATE"); - put("Text", "TEXT"); + put("Text", "TEXT"); put ("Dropdown", "INT"); }}; private final JdbcTemplate jdbcTemplate; @@ -292,22 +292,34 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } } + private String datatableColumnNameToCodeValueName(final String columnName, final String code) { + + return (code + "_cd_" + columnName); + } + private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder) { String name = (column.has("name")) ? column.get("name").getAsString() : null; String type = (column.has("type")) ? column.get("type").getAsString() : null; Integer length = (column.has("length")) ? column.get("length").getAsInt() : null; Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; - + String code = (column.has("code")) ? column.get("code").getAsString() : null; + + if (StringUtils.isNotBlank(code)) { + name = datatableColumnNameToCodeValueName(name, code); + } + String mysqlType = apiTypeToMySQL.get(type); sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType); - + if (type != null) { if (type.equals("String")) { sqlBuilder = sqlBuilder.append("(" + length + ")"); } else if (type.equals("Decimal")) { sqlBuilder = sqlBuilder.append("(19,6)"); + } else if (type.equals("Dropdown")) { + sqlBuilder = sqlBuilder.append("(11)"); } } if (mandatory != null) { @@ -317,7 +329,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ sqlBuilder = sqlBuilder.append(" DEFAULT NULL"); } } - + sqlBuilder = sqlBuilder.append(", "); } @@ -326,29 +338,29 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ public CommandProcessingResult createDatatable(final JsonCommand command) { String datatableName = null; - + try { context.authenticatedUser(); this.fromApiJsonDeserializer.validateForCreate(command.json()); - + JsonElement element = this.fromJsonHelper.parse(command.json()); JsonArray columns = this.fromJsonHelper.extractJsonArrayNamed("columns", element); datatableName = this.fromJsonHelper.extractStringNamed("datatableName", element); String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element); Boolean multiRow = this.fromJsonHelper.extractBooleanNamed("multiRow", element); - + if (multiRow == null) { multiRow = false; } - + validateDatatableName(datatableName); validateAppTable(apptableName); - + String fkColumnName = apptableName.substring(2) + "_id"; String fkName = datatableName.toLowerCase().replaceAll("\\s", "_"); StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` ("); - + if (multiRow) { sqlBuilder = sqlBuilder .append("`id` BIGINT(20) NOT NULL AUTO_INCREMENT, ") @@ -357,14 +369,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ sqlBuilder = sqlBuilder .append("`" + fkColumnName + "` BIGINT(20) NOT NULL, "); } - + for (JsonElement column : columns) { parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder); } - + // Remove trailing comma and space sqlBuilder = sqlBuilder.delete(sqlBuilder.length() - 2, sqlBuilder.length()); - + if (multiRow) { sqlBuilder = sqlBuilder .append(", PRIMARY KEY (`id`)") @@ -372,7 +384,6 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ .append(", CONSTRAINT `fk_" + fkName + "` ") .append("FOREIGN KEY (`" + fkColumnName + "`) ") .append("REFERENCES `" + apptableName + "` (`id`)"); - } else { sqlBuilder = sqlBuilder .append(", PRIMARY KEY (`" + fkColumnName + "`)") @@ -380,10 +391,10 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ .append("FOREIGN KEY (`" + fkColumnName + "`) ") .append("REFERENCES `" + apptableName + "` (`id`)"); } - + sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;"); this.jdbcTemplate.execute(sqlBuilder.toString()); - + registerDatatable(datatableName, apptableName); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); @@ -400,25 +411,43 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ .build(); } - private void parseDatatableColumnForUpdate(final JsonObject column, StringBuilder sqlBuilder) { - + private void parseDatatableColumnForUpdate(final JsonObject column, + final Map mapColumnNameDefinition, StringBuilder sqlBuilder) { + String name = (column.has("name")) ? column.get("name").getAsString() : null; String newName = (column.has("newName")) ? column.get("newName").getAsString() : name; - String type = (column.has("type")) ? column.get("type").getAsString() : null; - Integer length = (column.has("length")) ? column.get("length").getAsInt() : null; Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; String after = (column.has("after")) ? column.get("after").getAsString() : null; - - String mysqlType = apiTypeToMySQL.get(type); - sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + mysqlType); - - if (type != null) { - if (type.equals("String") && length != null) { - sqlBuilder = sqlBuilder.append("(" + length + ")"); - } else if (type.equals("Decimal")) { - sqlBuilder = sqlBuilder.append("(19,6)"); + String code = (column.has("code")) ? column.get("code").getAsString() : null; + String newCode = (column.has("newCode")) ? column.get("newCode").getAsString() : null; + + if (StringUtils.isNotBlank(code)) { + name = datatableColumnNameToCodeValueName(name, code); + + if (StringUtils.isNotBlank(newCode)) { + newName = datatableColumnNameToCodeValueName(newName, newCode); + } else { + newName = datatableColumnNameToCodeValueName(newName, code); } } + + if (!mapColumnNameDefinition.containsKey(name)) { + throw new PlatformDataIntegrityException("error.msg.datatable.column.missing.update.parse", + "Column " + name + " does not exist.", name); + } + + String type = mapColumnNameDefinition.get(name).getColumnType(); + Long length = mapColumnNameDefinition.get(name).getColumnLength(); + + sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + type); + if (length != null && length > 0) { + if (type.equals("DECIMAL")) { + sqlBuilder.append("(19,6)"); + } else if (type.equals("VARCHAR")) { + sqlBuilder.append("(" + length + ")"); + } + } + if (mandatory != null) { if (mandatory) { sqlBuilder = sqlBuilder.append(" NOT NULL"); @@ -432,21 +461,28 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder) { - + String name = (column.has("name")) ? column.get("name").getAsString() : null; String type = (column.has("type")) ? column.get("type").getAsString() : null; Integer length = (column.has("length")) ? column.get("length").getAsInt() : null; Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; String after = (column.has("after")) ? column.get("after").getAsString() : null; - + String code = (column.has("code")) ? column.get("code").getAsString() : null; + + if (StringUtils.isNotBlank(code)) { + name = datatableColumnNameToCodeValueName(name, code); + } + String mysqlType = apiTypeToMySQL.get(type); sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType); - + if (type != null) { if (type.equals("String") && length != null) { sqlBuilder = sqlBuilder.append("(" + length + ")"); } else if (type.equals("Decimal")) { sqlBuilder = sqlBuilder.append("(19,6)"); + } else if (type.equals("Dropdown")) { + sqlBuilder = sqlBuilder.append("(11)"); } } if (mandatory != null) { @@ -462,9 +498,9 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder) { - + String name = (column.has("name")) ? column.get("name").getAsString() : null; - + sqlBuilder = sqlBuilder.append(", DROP COLUMN `" + name + "`"); } @@ -475,59 +511,95 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ try { context.authenticatedUser(); this.fromApiJsonDeserializer.validateForUpdate(command.json()); - + JsonElement element = this.fromJsonHelper.parse(command.json()); JsonArray changeColumns = this.fromJsonHelper.extractJsonArrayNamed("changeColumns", element); JsonArray addColumns = this.fromJsonHelper.extractJsonArrayNamed("addColumns", element); JsonArray dropColumns = this.fromJsonHelper.extractJsonArrayNamed("dropColumns", element); String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element); - + validateDatatableName(datatableName); - + + List columnHeaderData = this.genericDataService.fillResultsetColumnHeaders(datatableName); + Map mapColumnNameDefinition = new HashMap(); + for (ResultsetColumnHeaderData columnHeader : columnHeaderData) { + mapColumnNameDefinition.put(columnHeader.getColumnName(), columnHeader); + } + if (!StringUtils.isBlank(apptableName)) { validateAppTable(apptableName); - + String oldApptableName = this.queryForApplicationTableName(datatableName); if (!StringUtils.equals(oldApptableName, apptableName)) { deregisterDatatable(datatableName); registerDatatable(datatableName, apptableName); } } - + if (changeColumns == null && addColumns == null && dropColumns == null) { return; } - - StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`"); - - if (changeColumns != null) { - for (JsonElement column : changeColumns) { - parseDatatableColumnForUpdate(column.getAsJsonObject(), sqlBuilder); - } - } - if (addColumns != null) { - for (JsonElement column : addColumns) { - parseDatatableColumnForAdd(column.getAsJsonObject(), sqlBuilder); - } - } + if (dropColumns != null) { + + StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`"); + for (JsonElement column : dropColumns) { parseDatatableColumnForDrop(column.getAsJsonObject(), sqlBuilder); } + + // Remove the first comma, right after ALTER TABLE `datatable` + int indexOfFirstComma = sqlBuilder.indexOf(","); + if (indexOfFirstComma != -1) { + sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); + } + + this.jdbcTemplate.execute(sqlBuilder.toString()); } - - // Remove the first comma, right after ALTER TABLE `datatable` - int indexOfFirstComma = sqlBuilder.indexOf(","); - if (indexOfFirstComma != -1) { - sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); + if (addColumns != null) { + + StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`"); + + for (JsonElement column : addColumns) { + parseDatatableColumnForAdd(column.getAsJsonObject(), sqlBuilder); + } + + // Remove the first comma, right after ALTER TABLE `datatable` + int indexOfFirstComma = sqlBuilder.indexOf(","); + if (indexOfFirstComma != -1) { + sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); + } + + this.jdbcTemplate.execute(sqlBuilder.toString()); + } + if (changeColumns != null) { + + StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`"); + + for (JsonElement column : changeColumns) { + parseDatatableColumnForUpdate(column.getAsJsonObject(), mapColumnNameDefinition, sqlBuilder); + } + + // Remove the first comma, right after ALTER TABLE `datatable` + int indexOfFirstComma = sqlBuilder.indexOf(","); + if (indexOfFirstComma != -1) { + sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); + } + + this.jdbcTemplate.execute(sqlBuilder.toString()); } - this.jdbcTemplate.execute(sqlBuilder.toString()); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); - if (realCause.getMessage().contains("Unknown column")) { + if (realCause.getMessage().toLowerCase().contains("unknown column")) { throw new PlatformDataIntegrityException( - "error.msg.datatable.column.missing", realCause.getMessage()); + "error.msg.datatable.column.missing.update", realCause.getMessage()); + } else if (realCause.getMessage().toLowerCase().contains("can't drop")) { + throw new PlatformDataIntegrityException( + "error.msg.datatable.column.missing.drop", realCause.getMessage()); + } else if (realCause.getMessage().toLowerCase().contains("duplicate column")) { + throw new PlatformDataIntegrityException( + "error.msg.datatable.duplicate.column", realCause.getMessage()); } } } @@ -535,18 +607,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ @Transactional @Override public void deleteDatatable(final String datatableName) { - + try { context.authenticatedUser(); - + validateDatatableName(datatableName); deregisterDatatable(datatableName); - + String sql = "DROP TABLE `" + datatableName + "`"; this.jdbcTemplate.execute(sql); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); - + if (realCause.getMessage().contains("Unknown table")) { throw new PlatformDataIntegrityException( "error.msg.datatable.missing", realCause.getMessage());