From 106fc2d56d12f269d0548b06974e1f713dfa5a7d Mon Sep 17 00:00:00 2001 From: Lech Rozanski Date: Tue, 11 Jun 2013 15:00:01 +0200 Subject: [PATCH] MIFOSX-365: Simplify 'Data Tables' functionality --- .../core/data/DataValidatorBuilder.java | 2 +- ...tatableCommandFromApiJsonDeserializer.java | 34 ++++++-- .../ReadWriteNonCoreDataServiceImpl.java | 84 +++++++++++++++---- 3 files changed, 93 insertions(+), 27 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 313377e10..02c9f8d00 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -79,7 +79,7 @@ public class DataValidatorBuilder { public void failWithCode(final String errorCode) { StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(this.parameter) - .append(errorCode); + .append(".").append(errorCode); StringBuilder defaultEnglishMessage = new StringBuilder("Failed data validation due to: ").append(errorCode).append("."); ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), this.parameter, this.value); 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 1a214ca61..3ea531505 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 @@ -43,7 +43,7 @@ public class DatatableCommandFromApiJsonDeserializer { private final Set supportedParametersForAddColumns = new HashSet(Arrays.asList( "name", "type", "length", "mandatory", "after", "code")); private final Set supportedParametersForChangeColumns = new HashSet(Arrays.asList( - "name", "newName", "mandatory", "after", "code", "newCode")); + "name", "newName", "length", "mandatory", "after", "code", "newCode")); private final Set supportedParametersForDropColumns = new HashSet(Arrays.asList( "name")); private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date", "Text", "Dropdown" }; @@ -65,7 +65,7 @@ public class DatatableCommandFromApiJsonDeserializer { if (type != null && type.equals("String")) { if (length != null) { baseDataValidator.reset().parameter("length").value(length).notBlank() - .zeroOrPositiveAmount(); + .positiveAmount(); } else { baseDataValidator.reset().parameter("length").value(length) .cantBeBlankWhenParameterProvidedIs("type", type); @@ -78,8 +78,13 @@ public class DatatableCommandFromApiJsonDeserializer { 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); + if (code != null) { + baseDataValidator.reset().parameter("code").value(code).notBlank() + .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + } else { + baseDataValidator.reset().parameter("code").value(code) + .cantBeBlankWhenParameterProvidedIs("type", type); + } } else { baseDataValidator.reset().parameter("code").value(code).mustBeBlankWhenParameterProvided("type", type); } @@ -95,15 +100,16 @@ public class DatatableCommandFromApiJsonDeserializer { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); final JsonElement element = fromApiJsonHelper.parse(json); + + final String datatableName = fromApiJsonHelper.extractStringNamed("datatableName", element); + baseDataValidator.reset().parameter("datatableName").value(datatableName).notBlank().notExceedingLengthOf(50) + .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element); baseDataValidator.reset().parameter("apptableName").value(apptableName).notBlank().notExceedingLengthOf(50) .isOneOfTheseValues(supportedApptableNames); final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : ""; - final String datatableName = fromApiJsonHelper.extractStringNamed("datatableName", element); - baseDataValidator.reset().parameter("datatableName").value(datatableName).notBlank().notExceedingLengthOf(50) - .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); - final Boolean multiRow = fromApiJsonHelper.extractBooleanNamed("multiRow", element); baseDataValidator.reset().parameter("multiRow").value(multiRow).ignoreIfNull().notBlank() .isOneOfTheseValues(true, false); @@ -170,6 +176,18 @@ public class DatatableCommandFromApiJsonDeserializer { .notExceedingLengthOf(50).isNotOneOfTheseValues("id", fkColumnName) .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + if (fromApiJsonHelper.parameterExists("length", column)) { + final String lengthStr = fromApiJsonHelper.extractStringNamed("length", column); + if (StringUtils.isWhitespace(lengthStr) || !StringUtils.isNumeric(lengthStr) + || StringUtils.isBlank(lengthStr)) { + baseDataValidator.reset().parameter("length").failWithCode("not.greater.than.zero"); + } else { + Integer length = Integer.parseInt(lengthStr); + baseDataValidator.reset().parameter("length").value(length).ignoreIfNull().notBlank() + .positiveAmount(); + } + } + final String code = fromApiJsonHelper.extractStringNamed("code", column); baseDataValidator.reset().parameter("code").value(code).ignoreIfNull().notBlank() .notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); 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 a46f1b5bf..d19c35c46 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 @@ -24,6 +24,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer; @@ -297,6 +298,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ return (code + "_cd_" + columnName); } + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } + private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder) { String name = (column.has("name")) ? column.get("name").getAsString() : null; @@ -357,7 +363,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ validateAppTable(apptableName); String fkColumnName = apptableName.substring(2) + "_id"; - String fkName = datatableName.toLowerCase().replaceAll("\\s", "_"); + String fkName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + fkColumnName; StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` ("); @@ -380,7 +386,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (multiRow) { sqlBuilder = sqlBuilder .append(", PRIMARY KEY (`id`)") - .append(", KEY `fk_" + apptableName + "` (`" + fkColumnName + "`)") + .append(", KEY `fk_" + apptableName.substring(2) + "_id` (`" + fkColumnName + "`)") .append(", CONSTRAINT `fk_" + fkName + "` ") .append("FOREIGN KEY (`" + fkColumnName + "`) ") .append("REFERENCES `" + apptableName + "` (`id`)"); @@ -398,11 +404,17 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ registerDatatable(datatableName, apptableName); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); - - if (realCause.getMessage().contains("Table") && realCause.getMessage().contains("already exists")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.already.exists", realCause.getMessage()); - } + List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); + + if (realCause.getMessage().toLowerCase().contains("duplicate column name")) { + baseDataValidator.reset().parameter("name").failWithCode("duplicate.column.name"); + } else if (realCause.getMessage().contains("Table") && realCause.getMessage().contains("already exists")) { + baseDataValidator.reset().parameter("datatableName").value(datatableName) + .failWithCode("datatable.already.exists"); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); } return new CommandProcessingResultBuilder() @@ -415,6 +427,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final Map mapColumnNameDefinition, StringBuilder sqlBuilder) { String name = (column.has("name")) ? column.get("name").getAsString() : null; + String lengthStr = (column.has("length")) ? column.get("length").getAsString() : null; + Integer length = (StringUtils.isNotBlank(lengthStr)) ? Integer.parseInt(lengthStr) : null; String newName = (column.has("newName")) ? column.get("newName").getAsString() : name; Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; String after = (column.has("after")) ? column.get("after").getAsString() : null; @@ -437,13 +451,15 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } String type = mapColumnNameDefinition.get(name).getColumnType(); - Long length = mapColumnNameDefinition.get(name).getColumnLength(); + if (length == null && type.toLowerCase().equals("varchar")) { + length = mapColumnNameDefinition.get(name).getColumnLength().intValue(); + } sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + type); if (length != null && length > 0) { - if (type.equals("DECIMAL")) { + if (type.toLowerCase().equals("decimal")) { sqlBuilder.append("(19,6)"); - } else if (type.equals("VARCHAR")) { + } else if (type.toLowerCase().equals("varchar")) { sqlBuilder.append("(" + length + ")"); } } @@ -531,6 +547,34 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ String oldApptableName = this.queryForApplicationTableName(datatableName); if (!StringUtils.equals(oldApptableName, apptableName)) { + String oldFKName = oldApptableName.substring(2) + "_id"; + String newFKName = apptableName.substring(2) + "_id"; + String oldConstraintName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + oldFKName; + String newConstraintName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + newFKName; + StringBuilder sqlBuilder = new StringBuilder(); + + if (mapColumnNameDefinition.containsKey("id")) { + sqlBuilder = sqlBuilder + .append("ALTER TABLE `" + datatableName + "` ") + .append("DROP KEY `fk_" + oldFKName + "`,") + .append("DROP FOREIGN KEY `fk_" + oldConstraintName + "`,") + .append("CHANGE COLUMN `" + oldFKName + "` `" + newFKName + "` BIGINT(20) NOT NULL,") + .append("ADD KEY `fk_" + newFKName + "` (`" + newFKName + "`),") + .append("ADD CONSTRAINT `fk_" + newConstraintName + "` ") + .append("FOREIGN KEY (`" + newFKName + "`) ") + .append("REFERENCES `" + apptableName + "` (`id`)"); + } else { + sqlBuilder = sqlBuilder + .append("ALTER TABLE `" + datatableName + "` ") + .append("DROP FOREIGN KEY `fk_" + oldConstraintName + "`,") + .append("CHANGE COLUMN `" + oldFKName + "` `" + newFKName + "` BIGINT(20) NOT NULL,") + .append("ADD CONSTRAINT `fk_" + newConstraintName + "` ") + .append("FOREIGN KEY (`" + newFKName + "`) ") + .append("REFERENCES `" + apptableName + "` (`id`)"); + } + + this.jdbcTemplate.execute(sqlBuilder.toString()); + deregisterDatatable(datatableName); registerDatatable(datatableName, apptableName); } @@ -590,17 +634,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); + List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); if (realCause.getMessage().toLowerCase().contains("unknown column")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.column.missing.update", realCause.getMessage()); + baseDataValidator.reset().parameter("name").failWithCode("does.not.exist"); } else if (realCause.getMessage().toLowerCase().contains("can't drop")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.column.missing.drop", realCause.getMessage()); + baseDataValidator.reset().parameter("name").failWithCode("does.not.exist"); } else if (realCause.getMessage().toLowerCase().contains("duplicate column")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.duplicate.column", realCause.getMessage()); + baseDataValidator.reset().parameter("name").failWithCode("column.already.exists"); } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); } } @@ -618,11 +663,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ this.jdbcTemplate.execute(sql); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); + List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); if (realCause.getMessage().contains("Unknown table")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.missing", realCause.getMessage()); + baseDataValidator.reset().parameter("datatableName").failWithCode("does.not.exist"); } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); } }