diff --git a/mifosng-db/migrations/core_db/V107__datatable_code_mappings.sql b/mifosng-db/migrations/core_db/V107__datatable_code_mappings.sql new file mode 100755 index 000000000..6b40e21f8 --- /dev/null +++ b/mifosng-db/migrations/core_db/V107__datatable_code_mappings.sql @@ -0,0 +1,11 @@ +CREATE TABLE `x_table_cloumn_code_mappings` ( + `column_alias_name` VARCHAR(50) NOT NULL, + `code_id` INT(10) NOT NULL, + PRIMARY KEY (`column_alias_name`), + INDEX `FK_x_code_id` (`code_id`), + CONSTRAINT `FK_x_code_id` FOREIGN KEY (`code_id`) REFERENCES `m_code` (`id`) +) +COLLATE='utf8_general_ci' +ENGINE=InnoDB; + +INSERT INTO `c_configuration` (`name`, `enabled`) VALUES ('constraint_approach_for_datatables', 1); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/data/CodeData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/data/CodeData.java index 11c340487..e5d598555 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/data/CodeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/data/CodeData.java @@ -10,7 +10,6 @@ package org.mifosplatform.infrastructure.codes.data; */ public class CodeData { - @SuppressWarnings("unused") private final Long id; @SuppressWarnings("unused") private final String name; @@ -26,4 +25,8 @@ public class CodeData { this.name = name; this.systemDefined = systemDefined; } + + public Long getCodeId(){ + return this.id; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformService.java index 6c7da48b6..2dc83bbac 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformService.java @@ -14,4 +14,6 @@ public interface CodeReadPlatformService { Collection retrieveAllCodes(); CodeData retrieveCode(Long codeId); + + CodeData retriveCode(String codeName); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformServiceImpl.java index efe6a09ac..187802cf4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeReadPlatformServiceImpl.java @@ -71,4 +71,18 @@ public class CodeReadPlatformServiceImpl implements CodeReadPlatformService { throw new CodeNotFoundException(codeId); } } + + @Override + public CodeData retriveCode(String codeName) { + try { + context.authenticatedUser(); + + final CodeMapper rm = new CodeMapper(); + final String sql = "select " + rm.schema() + " where c.code_name = ?"; + + return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { codeName }); + } catch (EmptyResultDataAccessException e) { + throw new CodeNotFoundException(codeName); + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeWritePlatformServiceJpaRepositoryImpl.java index 52807c4eb..fe8717f38 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/codes/service/CodeWritePlatformServiceJpaRepositoryImpl.java @@ -96,8 +96,13 @@ public class CodeWritePlatformServiceJpaRepositoryImpl implements CodeWritePlatf final Code code = retrieveCodeBy(codeId); if (code.isSystemDefined()) { throw new SystemDefinedCodeCannotBeChangedException(); } - this.codeRepository.delete(code); - + try { + this.codeRepository.delete(code); + this.codeRepository.flush(); + } catch (DataIntegrityViolationException e) { + throw new PlatformDataIntegrityException("error.msg.cund.unknown.data.integrity.issue", + "Unknown data integrity issue with resource: " + e.getMostSpecificCause()); + } return new CommandProcessingResultBuilder().withEntityId(codeId).build(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java index a343385c4..191926716 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -13,4 +13,5 @@ public interface ConfigurationDomainService { boolean isRescheduleRepaymentsOnHolidaysEnabled(); boolean allowTransactionsOnHolidayEnabled(); boolean allowTransactionsOnNonWorkingDayEnabled(); + boolean isConstraintApproachEnabledForDatatables(); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index c32cf223c..11c3f8ff2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -80,4 +80,11 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService return property.isEnabled(); } + @Override + public boolean isConstraintApproachEnabledForDatatables() { + final String colunnValue = "constraint_approach_for_datatables"; + final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByName(colunnValue); + if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(colunnValue); } + return property.isEnabled(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformDataIntegrityException.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformDataIntegrityException.java index c994217a6..07c25bfbd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformDataIntegrityException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformDataIntegrityException.java @@ -20,7 +20,7 @@ public class PlatformDataIntegrityException extends RuntimeException { final Object... defaultUserMessageArgs) { this.globalisationMessageCode = globalisationMessageCode; this.defaultUserMessage = defaultUserMessage; - this.parameterName = ""; + this.parameterName = null; this.defaultUserMessageArgs = defaultUserMessageArgs; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformServiceUnavailableException.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformServiceUnavailableException.java new file mode 100755 index 000000000..6fd03f5e3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/exception/PlatformServiceUnavailableException.java @@ -0,0 +1,11 @@ +package org.mifosplatform.infrastructure.core.exception; + +public class PlatformServiceUnavailableException extends AbstractPlatformServiceUnavailableException { + + public PlatformServiceUnavailableException(String globalisationMessageCode, String defaultUserMessage, + Object... defaultUserMessageArgs) { + super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs); + } + + +} 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 d1d84ff3b..edc870db3 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 @@ -24,6 +24,7 @@ public final class ResultsetColumnHeaderData { private final boolean isColumnPrimaryKey; private final List columnValues; + private final String columnCode; public static ResultsetColumnHeaderData basic(final String columnName, final String columnType) { @@ -31,25 +32,31 @@ public final class ResultsetColumnHeaderData { final boolean columnNullable = false; final boolean columnIsPrimaryKey = false; final List columnValues = new ArrayList(); - return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues); + final String columnCode = null; + return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues, + columnCode); } public static ResultsetColumnHeaderData detailed(final String columnName, final String columnType, final Long columnLength, - final boolean columnNullable, final boolean columnIsPrimaryKey, final List columnValues) { - return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues); + final boolean columnNullable, final boolean columnIsPrimaryKey, final List columnValues, + String columnCode) { + return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues, + columnCode); } private ResultsetColumnHeaderData(final String columnName, final String columnType, final Long columnLength, - final boolean columnNullable, final boolean columnIsPrimaryKey, final List columnValues) { + final boolean columnNullable, final boolean columnIsPrimaryKey, final List columnValues, + String columnCode) { this.columnName = columnName; this.columnType = columnType; this.columnLength = columnLength; this.isColumnNullable = columnNullable; this.isColumnPrimaryKey = columnIsPrimaryKey; this.columnValues = columnValues; + this.columnCode = columnCode; String displayType = null; - if (this.columnValues.isEmpty()) { + if (this.columnCode == null) { if (isString()) { displayType = "STRING"; } else if (isAnyInteger()) { @@ -228,4 +235,8 @@ public final class ResultsetColumnHeaderData { } return allowed; } + + public String getColumnCode() { + return this.columnCode; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java index 7eb592108..ce3cd7874 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/GenericDataServiceImpl.java @@ -185,14 +185,34 @@ public class GenericDataServiceImpl implements GenericDataService { boolean columnIsPrimaryKey = "PRI".equalsIgnoreCase(isPrimaryKey); List columnValues = new ArrayList(); + String codeName = null; if ("varchar".equalsIgnoreCase(columnType)) { - columnValues = retreiveColumnValues(columnName, "_cv"); + int codePosition = columnName.indexOf("_cv"); + if (codePosition > 0) { + codeName = columnName.substring(0, codePosition); + columnValues = retreiveColumnValues(codeName); + } + } else if ("int".equalsIgnoreCase(columnType)) { - columnValues = retreiveColumnValues(columnName, "_cd"); + int codePosition = columnName.indexOf("_cd"); + if (codePosition > 0) { + codeName = columnName.substring(0, codePosition); + columnValues = retreiveColumnValues(codeName); + } + } + if(codeName == null){ + final SqlRowSet rsValues = getDatatableCodeData(datatable, columnName); + Integer codeId = null; + while (rsValues.next()) { + codeId = rsValues.getInt("id"); + codeName = rsValues.getString("code_name"); + } + columnValues = retreiveColumnValues(codeId); + } final ResultsetColumnHeaderData rsch = ResultsetColumnHeaderData.detailed(columnName, columnType, columnLength, columnNullable, - columnIsPrimaryKey, columnValues); + columnIsPrimaryKey, columnValues,codeName); columnHeaders.add(rsch); } @@ -204,14 +224,10 @@ public class GenericDataServiceImpl implements GenericDataService { * Candidate for using caching there to get allowed 'column values' from * code/codevalue tables */ - private List retreiveColumnValues(final String columnName, final String code_suffix) { + private List retreiveColumnValues(final String codeName) { final List columnValues = new ArrayList(); - int codePosition = columnName.indexOf(code_suffix); - if (codePosition > 0) { - final String codeName = columnName.substring(0, codePosition); - final String sql = "select v.id, v.code_value from m_code m " + " join m_code_value v on v.code_id = m.id " + " where m.code_name = '" + codeName + "' order by v.order_position, v.id"; @@ -224,6 +240,23 @@ public class GenericDataServiceImpl implements GenericDataService { columnValues.add(new ResultsetColumnValueData(id, codeValue)); } + + return columnValues; + } + + private List retreiveColumnValues(final Integer codeId) { + + final List columnValues = new ArrayList(); + if (codeId != null) { + final String sql = "select v.id, v.code_value from m_code_value v where v.code_id =" + codeId + + " order by v.order_position, v.id"; + final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql); + rsValues.beforeFirst(); + while (rsValues.next()) { + final Integer id = rsValues.getInt("id"); + final String codeValue = rsValues.getString("code_value"); + columnValues.add(new ResultsetColumnValueData(id, codeValue)); + } } return columnValues; @@ -240,4 +273,13 @@ public class GenericDataServiceImpl implements GenericDataService { throw new DatatableNotFoundException(datatable); } + + private SqlRowSet getDatatableCodeData(final String datatable, String columnName) { + + final String sql = "select mc.id,mc.code_name from m_code mc join x_table_cloumn_code_mappings xcc on xcc.code_id = mc.id where xcc.column_alias_name='" + + datatable.toLowerCase().replaceAll("\\s", "_") + "_" + columnName + "'"; + final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql); + + return rsValues; + } } \ No newline at end of file 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 76500365f..06517b50f 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 @@ -18,8 +18,11 @@ import javax.sql.DataSource; import org.apache.commons.lang.StringUtils; import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.exception.GenericJDBCException; import org.hibernate.exception.SQLGrammarException; import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.codes.service.CodeReadPlatformService; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; @@ -27,6 +30,7 @@ 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.exception.PlatformServiceUnavailableException; import org.mifosplatform.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper; @@ -44,6 +48,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.jdbc.support.rowset.SqlRowSetMetaData; @@ -60,6 +65,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ 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 CODE_VALUES_TABLE = "m_code_value"; + private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class); private final static HashMap apiTypeToMySQL = new HashMap() { @@ -80,11 +87,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ private final JsonParserHelper helper; private final GenericDataService genericDataService; private final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer; + private final ConfigurationDomainService configurationDomainService; + private final CodeReadPlatformService codeReadPlatformService; @Autowired public ReadWriteNonCoreDataServiceImpl(final RoutingDataSource dataSource, final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService, - final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer) { + final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer, final CodeReadPlatformService codeReadPlatformService, + final ConfigurationDomainService configurationDomainService) { this.dataSource = dataSource; this.jdbcTemplate = new JdbcTemplate(this.dataSource); this.context = context; @@ -92,6 +102,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ this.helper = new JsonParserHelper(); this.genericDataService = genericDataService; this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.codeReadPlatformService = codeReadPlatformService; + this.configurationDomainService = configurationDomainService; } @Override @@ -253,7 +265,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'"; - String[] sqlArray = { deleteRolePermissionsSql, deletePermissionsSql, deleteRegisteredDatatableSql }; + String[] sqlArray = null; + if (configurationDomainService.isConstraintApproachEnabledForDatatables()) { + final String deleteColumnCodeSql = "delete from x_table_cloumn_code_mappings where column_alias_name like'" + + datatable.toLowerCase().replaceAll("\\s", "_") + "%'"; + sqlArray = new String[4]; + sqlArray[3] = deleteColumnCodeSql; + } else { + sqlArray = new String[3]; + } + sqlArray[0] = deleteRolePermissionsSql; + sqlArray[1] = deletePermissionsSql; + sqlArray[2] = deleteRegisteredDatatableSql; + this.jdbcTemplate.batchUpdate(sqlArray); } @@ -333,7 +357,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ "Validation errors exist.", dataValidationErrors); } } - private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder) { + private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder, StringBuilder constrainBuilder, + String dataTableNameAlias, Map codeMappings, boolean isConstraintApproach) { String name = (column.has("name")) ? column.get("name").getAsString() : null; String type = (column.has("type")) ? column.get("type").getAsString() : null; @@ -342,7 +367,13 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ String code = (column.has("code")) ? column.get("code").getAsString() : null; if (StringUtils.isNotBlank(code)) { - name = datatableColumnNameToCodeValueName(name, code); + if (isConstraintApproach) { + codeMappings.put(dataTableNameAlias + "_" + name, codeReadPlatformService.retriveCode(code).getCodeId()); + constrainBuilder.append(", CONSTRAINT `fk_").append(dataTableNameAlias).append("_").append(name).append("` ") + .append("FOREIGN KEY (`" + name + "`) ").append("REFERENCES `").append(CODE_VALUES_TABLE).append("` (`id`)"); + } else { + name = datatableColumnNameToCodeValueName(name, code); + } } String mysqlType = apiTypeToMySQL.get(type); @@ -399,10 +430,13 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ validateDatatableName(datatableName); validateAppTable(apptableName); - + boolean isConstraintApproach = configurationDomainService.isConstraintApproachEnabledForDatatables(); String fkColumnName = apptableName.substring(2) + "_id"; - String fkName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + fkColumnName; + final String dataTableNameAlias = datatableName.toLowerCase().replaceAll("\\s", "_"); + String fkName = dataTableNameAlias + "_" + fkColumnName; StringBuilder sqlBuilder = new StringBuilder(); + StringBuilder constrainBuilder = new StringBuilder(); + Map codeMappings = new HashMap(); sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` ("); if (multiRow) { @@ -413,7 +447,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } for (JsonElement column : columns) { - parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder); + parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder, constrainBuilder, dataTableNameAlias, + codeMappings, isConstraintApproach); } // Remove trailing comma and space @@ -429,10 +464,13 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ .append("FOREIGN KEY (`" + fkColumnName + "`) ").append("REFERENCES `" + actualAppTableName + "` (`id`)"); } + sqlBuilder.append(constrainBuilder); + sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;"); this.jdbcTemplate.execute(sqlBuilder.toString()); registerDatatable(datatableName, apptableName); + registerColumnCodeMapping(codeMappings); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); List dataValidationErrors = new ArrayList(); @@ -455,7 +493,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } private void parseDatatableColumnForUpdate(final JsonObject column, - final Map mapColumnNameDefinition, StringBuilder sqlBuilder) { + final Map mapColumnNameDefinition, StringBuilder sqlBuilder, String datatableName, + StringBuilder constrainBuilder, Map codeMappings, List removeMappings, boolean isConstraintApproach) { String name = (column.has("name")) ? column.get("name").getAsString() : null; String lengthStr = (column.has("length")) ? column.get("length").getAsString() : null; @@ -465,20 +504,53 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ String after = (column.has("after")) ? column.get("after").getAsString() : null; String code = (column.has("code")) ? column.get("code").getAsString() : null; String newCode = (column.has("newCode")) ? column.get("newCode").getAsString() : null; + String dataTableNameAlias = datatableName.toLowerCase().replaceAll("\\s", "_"); + if (isConstraintApproach) { + if (StringUtils.isBlank(newName)) { + newName = name; + } + if (!StringUtils.equalsIgnoreCase(code, newCode) || !StringUtils.equalsIgnoreCase(name, newName)) { + if (StringUtils.equalsIgnoreCase(code, newCode)) { + int codeId = getCodeIdForColumn(dataTableNameAlias, name); + if (codeId > 0) { + removeMappings.add(dataTableNameAlias + "_" + name); + constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(dataTableNameAlias).append("_").append(name).append("` "); + codeMappings.put(dataTableNameAlias + "_" + newName, (long) codeId); + constrainBuilder.append(",ADD CONSTRAINT `fk_").append(dataTableNameAlias).append("_").append(newName) + .append("` ").append("FOREIGN KEY (`" + newName + "`) ").append("REFERENCES `").append(CODE_VALUES_TABLE) + .append("` (`id`)"); + } - if (StringUtils.isNotBlank(code)) { - name = datatableColumnNameToCodeValueName(name, code); - - if (StringUtils.isNotBlank(newCode)) { - newName = datatableColumnNameToCodeValueName(newName, newCode); - } else { - newName = datatableColumnNameToCodeValueName(newName, code); + } else { + if (code != null) { + removeMappings.add(dataTableNameAlias + "_" + name); + if (newCode == null) { + constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(dataTableNameAlias).append("_").append(name) + .append("` "); + } + } + if (newCode != null) { + codeMappings.put(dataTableNameAlias + "_" + newName, codeReadPlatformService.retriveCode(newCode).getCodeId()); + if (code == null) { + constrainBuilder.append(",ADD CONSTRAINT `fk_").append(dataTableNameAlias).append("_").append(newName) + .append("` ").append("FOREIGN KEY (`" + newName + "`) ").append("REFERENCES `") + .append(CODE_VALUES_TABLE).append("` (`id`)"); + } + } + } + } + } else { + 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(); if (length == null && type.toLowerCase().equals("varchar")) { length = mapColumnNameDefinition.get(name).getColumnLength().intValue(); @@ -505,7 +577,22 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } } - private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder) { + private int getCodeIdForColumn(String dataTableNameAlias, String name) { + StringBuilder checkColumnCodeMapping = new StringBuilder(); + checkColumnCodeMapping.append("select ccm.code_id from x_table_cloumn_code_mappings ccm where ccm.column_alias_name='") + .append(dataTableNameAlias).append("_").append(name).append("'"); + int codeId = 0; + try { + codeId = this.jdbcTemplate.queryForInt(checkColumnCodeMapping.toString()); + } catch (EmptyResultDataAccessException e) { + logger.info(e.getMessage()); + } + return codeId; + } + + + private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder, String dataTableNameAlias, + StringBuilder constrainBuilder, Map codeMappings, boolean isConstraintApproach) { String name = (column.has("name")) ? column.get("name").getAsString() : null; String type = (column.has("type")) ? column.get("type").getAsString() : null; @@ -515,7 +602,13 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ String code = (column.has("code")) ? column.get("code").getAsString() : null; if (StringUtils.isNotBlank(code)) { - name = datatableColumnNameToCodeValueName(name, code); + if (isConstraintApproach) { + codeMappings.put(dataTableNameAlias + "_" + name, codeReadPlatformService.retriveCode(code).getCodeId()); + constrainBuilder.append(",ADD CONSTRAINT `fk_").append(dataTableNameAlias).append("_").append(name).append("` ") + .append("FOREIGN KEY (`" + name + "`) ").append("REFERENCES `").append(CODE_VALUES_TABLE).append("` (`id`)"); + } else { + name = datatableColumnNameToCodeValueName(name, code); + } } String mysqlType = apiTypeToMySQL.get(type); @@ -542,11 +635,47 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } } - private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder) { - + private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder, String datatableName, + StringBuilder constrainBuilder, List codeMappings) { + String datatableAlias = datatableName.toLowerCase().replaceAll("\\s", "_"); String name = (column.has("name")) ? column.get("name").getAsString() : null; - sqlBuilder = sqlBuilder.append(", DROP COLUMN `" + name + "`"); + StringBuilder findFKSql = new StringBuilder(); + findFKSql.append("SELECT count(*)").append("FROM information_schema.TABLE_CONSTRAINTS i") + .append(" WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY'").append(" AND i.TABLE_SCHEMA = DATABASE()") + .append(" AND i.TABLE_NAME = '").append(datatableName).append("' AND i.CONSTRAINT_NAME = 'fk_").append(datatableAlias) + .append("_").append(name).append("' "); + int count = this.jdbcTemplate.queryForInt(findFKSql.toString()); + if (count > 0) { + codeMappings.add(datatableAlias + "_" + name); + constrainBuilder.append(", DROP FOREIGN KEY `fk_").append(datatableAlias).append("_").append(name).append("` "); + } + } + + private void registerColumnCodeMapping(Map codeMappings) { + if (codeMappings != null && !codeMappings.isEmpty()) { + String[] addSqlList = new String[codeMappings.size()]; + int i = 0; + for (Map.Entry mapEntry : codeMappings.entrySet()) { + addSqlList[i++] = "insert into x_table_cloumn_code_mappings (column_alias_name, code_id) values ('" + mapEntry.getKey() + + "'," + mapEntry.getValue() + ");"; + } + + this.jdbcTemplate.batchUpdate(addSqlList); + } + } + + private void deleteColumnCodeMapping(List columnNames) { + if (columnNames != null && !columnNames.isEmpty()) { + String[] deleteSqlList = new String[columnNames.size()]; + int i = 0; + for (String columnName : columnNames) { + deleteSqlList[i++] = "DELETE FROM x_table_cloumn_code_mappings WHERE column_alias_name='" + columnName + "';"; + } + + this.jdbcTemplate.batchUpdate(deleteSqlList); + } + } @Transactional @@ -571,6 +700,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ mapColumnNameDefinition.put(columnHeader.getColumnName(), columnHeader); } + boolean isConstraintApproach = configurationDomainService.isConstraintApproachEnabledForDatatables(); + if (!StringUtils.isBlank(apptableName)) { validateAppTable(apptableName); @@ -610,9 +741,10 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (dropColumns != null) { StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`"); - + StringBuilder constrainBuilder = new StringBuilder(); + List codeMappings = new ArrayList(); for (JsonElement column : dropColumns) { - parseDatatableColumnForDrop(column.getAsJsonObject(), sqlBuilder); + parseDatatableColumnForDrop(column.getAsJsonObject(), sqlBuilder, datatableName, constrainBuilder, codeMappings); } // Remove the first comma, right after ALTER TABLE `datatable` @@ -620,15 +752,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (indexOfFirstComma != -1) { sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); } - + sqlBuilder.append(constrainBuilder); this.jdbcTemplate.execute(sqlBuilder.toString()); + deleteColumnCodeMapping(codeMappings); } if (addColumns != null) { StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`"); - + StringBuilder constrainBuilder = new StringBuilder(); + Map codeMappings = new HashMap(); for (JsonElement column : addColumns) { - parseDatatableColumnForAdd(column.getAsJsonObject(), sqlBuilder); + parseDatatableColumnForAdd(column.getAsJsonObject(), sqlBuilder, datatableName.toLowerCase().replaceAll("\\s", "_"), + constrainBuilder, codeMappings, isConstraintApproach); } // Remove the first comma, right after ALTER TABLE `datatable` @@ -636,15 +771,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (indexOfFirstComma != -1) { sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); } - + sqlBuilder.append(constrainBuilder); this.jdbcTemplate.execute(sqlBuilder.toString()); + registerColumnCodeMapping(codeMappings); } if (changeColumns != null) { StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`"); - + StringBuilder constrainBuilder = new StringBuilder(); + Map codeMappings = new HashMap(); + List removeMappings = new ArrayList(); for (JsonElement column : changeColumns) { - parseDatatableColumnForUpdate(column.getAsJsonObject(), mapColumnNameDefinition, sqlBuilder); + parseDatatableColumnForUpdate(column.getAsJsonObject(), mapColumnNameDefinition, sqlBuilder, datatableName, + constrainBuilder, codeMappings, removeMappings, isConstraintApproach); } // Remove the first comma, right after ALTER TABLE `datatable` @@ -652,8 +791,15 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (indexOfFirstComma != -1) { sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); } - - this.jdbcTemplate.execute(sqlBuilder.toString()); + sqlBuilder.append(constrainBuilder); + try { + this.jdbcTemplate.execute(sqlBuilder.toString()); + deleteColumnCodeMapping(removeMappings); + registerColumnCodeMapping(codeMappings); + } catch (GenericJDBCException e) { + if (e.getMessage().contains("Error on rename")) { throw new PlatformServiceUnavailableException( + "error.msg.datatable.column.update.not.allowed", "One of the column name modification not allowed"); } + } } } catch (SQLGrammarException e) { Throwable realCause = e.getCause();