MIFOSX-365: Simplify 'Data Tables' functionality

This commit is contained in:
Lech Rozanski
2013-06-04 14:48:41 +02:00
parent e99834bd83
commit 16996b5e8c
4 changed files with 454 additions and 145 deletions
+249 -16
View File
@@ -1063,11 +1063,19 @@
<tr> <tr>
<td><a href="#datatables">Data Table</a></td> <td><a href="#datatables">Data Table</a></td>
<td>datatables</td> <td>datatables</td>
<td></td> <td><a href="#datatables_createTable">Create Data Table</a></td>
<td><a href="#datatables_list">List Data Tables</a></td> <td><a href="#datatables_list">List Data Tables</a></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td></td>
<td>datatables/{datatable}</td>
<td></td>
<td><a href="#datatables_getTable">Retrieve Data Table Details</a></td>
<td><a href="#datatables_updateTable">Update Data Table</a></td>
<td><a href="#datatables_deleteTable">Delete Data Table</a></td>
</tr>
<tr class="alt"> <tr class="alt">
<td></td> <td></td>
<td>datatables/register/{datatable}/{apptable}</td> <td>datatables/register/{datatable}/{apptable}</td>
@@ -11276,6 +11284,57 @@ No Request Body:
</div> </div>
</div> </div>
<a id="datatables_createTable" name="datatables_createTable"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Create Data Table</h4>
<p>Create a new data table and registers it with the Mifos X Core application
table.</p>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/datatables
</code>
<code class="method-request">
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
}
]
}
</code>
<code class="method-response">
{
"resourceIdentifier": "extra_client_details"
}
</code>
</div>
</div>
<a id="datatables_list" name="datatables_list" <a id="datatables_list" name="datatables_list"
class="old-syle-anchor">&nbsp;</a> class="old-syle-anchor">&nbsp;</a>
@@ -11303,25 +11362,191 @@ No Request Body:
<code class="method-declaration"> <code class="method-declaration">
GET https://DomainName/api/v1/datatables GET https://DomainName/api/v1/datatables
</code> </code>
<code class="method-response"> <code class="method-response">
[ [
{ {
"applicationTableName": "m_client", "applicationTableName": "m_client",
"registeredTableName": "Additional Client Fields Data" "registeredTableName": "extra_client_details",
}, "columnHeaderData": [
{ {
"applicationTableName": "m_client", "columnName": "client_id",
"registeredTableName": "Extra Family Details Data" "columnType": "bigint",
}, "columnLength": 0,
{ "columnDisplayType": "INTEGER",
"applicationTableName": "m_loan", "isColumnNullable": false,
"registeredTableName": "Additional Loan Fields Data" "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": []
}
]
}
]
</code> </code>
</div> </div>
</div> </div>
<a id="datatables_getTable" name="datatables_getTable"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve Data Table Details</h4>
<p>Lists a registered data table details and the Mifos X Core
application table they are registered to.</p>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/datatables/{datatable}
</code>
<code class="method-response">
{
"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": []
}
]
}
</code>
</div>
</div>
<a id="datatables_updateTable" name="datatables_updateTable"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Update Data Table</h4>
<p>Modifies fields of a data table. If the apptableName parameter is passed,
data table is deregistered and registered with the new application table.</p>
</div>
<div class="method-example">
<code class="method-declaration">
PUT https://DomainName/api/v1/datatables/{datatables}
</code>
<code class="method-request">
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
}
]
}
</code>
<code class="method-response">
{
"resourceIdentifier": "extra_client_details"
}
</code>
</div>
</div>
<a id="datatables_deleteTable" name="datatables_deleteTable"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Delete Data Table</h4>
<p>Deletes a data table and unregisters it from the Mifos X Core
application table.</p>
</div>
<div class="method-example">
<code class="method-declaration">
DELETE https://DomainName/api/v1/datatables/{datatables}
</code>
<code class="method-request">
DELETE https://DomainName/api/v1/datatables/extra_client_details
Content-Type: application/json
No Request Body
</code>
<code class="method-response">
{
"resourceIdentifier": "extra_client_details"
}
</code>
</div>
</div>
<a id="datatables_register" name="datatables_register" <a id="datatables_register" name="datatables_register"
class="old-syle-anchor">&nbsp;</a> class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
@@ -15357,11 +15582,19 @@ GET https://DomainName/api/v1/search?query=000000111
<tr class="alt"> <tr class="alt">
<td><a href="#datatables">Data Table</a></td> <td><a href="#datatables">Data Table</a></td>
<td>datatables</td> <td>datatables</td>
<td></td> <td><a href="#datatables_createTable">Create Data Table</a></td>
<td><a href="#datatables_list">List Data Tables</a></td> <td><a href="#datatables_list">List Data Tables</a></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td></td>
<td>datatables/{datatable}</td>
<td></td>
<td><a href="#datatables_getTable">Retrieve Data Table Details</a></td>
<td><a href="#datatables_updateTable">Update Data Table</a></td>
<td><a href="#datatables_deleteTable">Delete Data Table</a></td>
</tr>
<tr> <tr>
<td></td> <td></td>
<td>datatables/register/{datatable}/{apptable}</td> <td>datatables/register/{datatable}/{apptable}</td>
@@ -18,6 +18,7 @@ import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -29,22 +30,23 @@ import com.google.gson.reflect.TypeToken;
public class DatatableCommandFromApiJsonDeserializer { 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_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. * The parameters supported for this command.
*/ */
private final Set<String> supportedParametersForCreate = new HashSet<String>(Arrays.asList( private final Set<String> supportedParametersForCreate = new HashSet<String>(Arrays.asList(
"datatableName", "apptableName", "multiRow", "columns")); "datatableName", "apptableName", "multiRow", "columns"));
private final Set<String> supportedParametersForCreateColumns = new HashSet<String>(Arrays.asList( private final Set<String> supportedParametersForCreateColumns = new HashSet<String>(Arrays.asList(
"name", "type", "length", "mandatory")); "name", "type", "length", "mandatory", "code"));
private final Set<String> supportedParametersForUpdate = new HashSet<String>(Arrays.asList( private final Set<String> supportedParametersForUpdate = new HashSet<String>(Arrays.asList(
"apptableName", "changeColumns", "addColumns", "dropColumns")); "apptableName", "changeColumns", "addColumns", "dropColumns"));
private final Set<String> supportedParametersForAddColumns = new HashSet<String>(Arrays.asList( private final Set<String> supportedParametersForAddColumns = new HashSet<String>(Arrays.asList(
"name", "type", "length", "mandatory", "after")); "name", "type", "length", "mandatory", "after", "code"));
private final Set<String> supportedParametersForChangeColumns = new HashSet<String>(Arrays.asList( private final Set<String> supportedParametersForChangeColumns = new HashSet<String>(Arrays.asList(
"name", "newName", "type", "length", "mandatory", "after")); "name", "newName", "mandatory", "after", "code", "newCode"));
private final Set<String> supportedParametersForDropColumns = new HashSet<String>(Arrays.asList( private final Set<String> supportedParametersForDropColumns = new HashSet<String>(Arrays.asList(
"name")); "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 Object[] supportedApptableNames = { "m_loan", "m_savings_account", "m_client", "m_group", "m_office" };
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@@ -54,6 +56,35 @@ public class DatatableCommandFromApiJsonDeserializer {
this.fromApiJsonHelper = fromApiJsonHelper; 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) { public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -83,28 +114,16 @@ public class DatatableCommandFromApiJsonDeserializer {
if (columns != null) { if (columns != null) {
for (JsonElement column : columns) { for (JsonElement column : columns) {
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForCreateColumns); fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForCreateColumns);
final String name = fromApiJsonHelper.extractStringNamed("name", column); final String name = fromApiJsonHelper.extractStringNamed("name", column);
baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) baseDataValidator.reset().parameter("name").value(name).notBlank()
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String type = fromApiJsonHelper.extractStringNamed("type", column); final String type = fromApiJsonHelper.extractStringNamed("type", column);
baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseValues(supportedColumnTypes); baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseValues(supportedColumnTypes);
final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column); validateType(baseDataValidator, 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 Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column); final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank() baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank()
.isOneOfTheseValues(true, false); .isOneOfTheseValues(true, false);
@@ -118,7 +137,10 @@ public class DatatableCommandFromApiJsonDeserializer {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
// Because all parameters are optional, a check to see if at least one parameter // 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 // 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<Map<String, Object>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForUpdate); fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForUpdate);
@@ -129,7 +151,7 @@ public class DatatableCommandFromApiJsonDeserializer {
final JsonElement element = fromApiJsonHelper.parse(json); final JsonElement element = fromApiJsonHelper.parse(json);
final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element); final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element);
baseDataValidator.reset().parameter("apptableName").value(apptableName).ignoreIfNull().notBlank() baseDataValidator.reset().parameter("apptableName").value(apptableName).ignoreIfNull().notBlank()
.notExceedingLengthOf(50).isOneOfTheseValues(supportedApptableNames); .isOneOfTheseValues(supportedApptableNames);
final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : ""; final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : "";
final JsonArray changeColumns = fromApiJsonHelper.extractJsonArrayNamed("changeColumns", element); final JsonArray changeColumns = fromApiJsonHelper.extractJsonArrayNamed("changeColumns", element);
@@ -140,36 +162,31 @@ public class DatatableCommandFromApiJsonDeserializer {
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForChangeColumns); fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForChangeColumns);
final String name = fromApiJsonHelper.extractStringNamed("name", column); final String name = fromApiJsonHelper.extractStringNamed("name", column);
baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) baseDataValidator.reset().parameter("name").value(name).notBlank()
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String newName = fromApiJsonHelper.extractStringNamed("newName", column); final String newName = fromApiJsonHelper.extractStringNamed("newName", column);
baseDataValidator.reset().parameter("newName").value(newName).ignoreIfNull().notBlank() baseDataValidator.reset().parameter("newName").value(newName).ignoreIfNull().notBlank()
.notExceedingLengthOf(50).isNotOneOfTheseValues("id", fkColumnName) .notExceedingLengthOf(50).isNotOneOfTheseValues("id", fkColumnName)
.matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
final String type = fromApiJsonHelper.extractStringNamed("type", column); final String code = fromApiJsonHelper.extractStringNamed("code", column);
baseDataValidator.reset().parameter("type").value(type).notBlank() baseDataValidator.reset().parameter("code").value(code).ignoreIfNull().notBlank()
.isOneOfTheseValues(supportedColumnTypes); .notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column); final String newCode = fromApiJsonHelper.extractStringNamed("newCode", column);
if (type != null && (type.equals("String") || type.equals("Text"))) { baseDataValidator.reset().parameter("newCode").value(newCode).ignoreIfNull().notBlank()
if (length != null) { .notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
baseDataValidator.reset().parameter("length").value(length).notBlank()
.zeroOrPositiveAmount(); if (StringUtils.isBlank(code) && StringUtils.isNotBlank(newCode)) {
} else if (!type.equals("Text")) { baseDataValidator.reset().parameter("code").value(code)
baseDataValidator.reset().parameter("length").value(length) .cantBeBlankWhenParameterProvidedIs("newCode", newCode);
.cantBeBlankWhenParameterProvidedIs("type", type);
}
} else if (type != null && length != null) {
baseDataValidator.reset().parameter("length").value(length)
.mustBeBlankWhenParameterProvidedIs("type", type);
} }
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column); final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank() baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank()
.isOneOfTheseValues(true, false); .isOneOfTheseValues(true, false);
final Boolean after = fromApiJsonHelper.extractBooleanNamed("after", column); final Boolean after = fromApiJsonHelper.extractBooleanNamed("after", column);
baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank() baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank()
.isOneOfTheseValues(true, false); .isOneOfTheseValues(true, false);
@@ -184,26 +201,10 @@ public class DatatableCommandFromApiJsonDeserializer {
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForAddColumns); fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForAddColumns);
final String name = fromApiJsonHelper.extractStringNamed("name", column); final String name = fromApiJsonHelper.extractStringNamed("name", column);
baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) baseDataValidator.reset().parameter("name").value(name).notBlank()
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String type = fromApiJsonHelper.extractStringNamed("type", column); validateType(baseDataValidator, 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);
}
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column); final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank() baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().notBlank()
@@ -223,8 +224,8 @@ public class DatatableCommandFromApiJsonDeserializer {
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForDropColumns); fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForDropColumns);
final String name = fromApiJsonHelper.extractStringNamed("name", column); final String name = fromApiJsonHelper.extractStringNamed("name", column);
baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(50) baseDataValidator.reset().parameter("name").value(name).notBlank()
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
} }
} }
@@ -17,7 +17,6 @@ public final class ResultsetColumnHeaderData {
private final String columnName; private final String columnName;
private final String columnType; private final String columnType;
@SuppressWarnings("unused")
private final Long columnLength; private final Long columnLength;
private final String columnDisplayType; private final String columnDisplayType;
private final boolean isColumnNullable; private final boolean isColumnNullable;
@@ -161,6 +160,10 @@ public final class ResultsetColumnHeaderData {
public String getColumnType() { public String getColumnType() {
return this.columnType; return this.columnType;
} }
public Long getColumnLength() {
return this.columnLength;
}
public String getColumnDisplayType() { public String getColumnDisplayType() {
return this.columnDisplayType; return this.columnDisplayType;
@@ -62,7 +62,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class); private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class);
private final static HashMap<String, String> apiTypeToMySQL = new HashMap<String, String>() {{ private final static HashMap<String, String> apiTypeToMySQL = new HashMap<String, String>() {{
put("String", "VARCHAR"); put("Number", "INT"); put("Decimal", "DECIMAL"); put("Date", "DATE"); 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; 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) { private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder) {
String name = (column.has("name")) ? column.get("name").getAsString() : null; String name = (column.has("name")) ? column.get("name").getAsString() : null;
String type = (column.has("type")) ? column.get("type").getAsString() : null; String type = (column.has("type")) ? column.get("type").getAsString() : null;
Integer length = (column.has("length")) ? column.get("length").getAsInt() : null; Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; 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); String mysqlType = apiTypeToMySQL.get(type);
sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType); sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType);
if (type != null) if (type != null)
{ {
if (type.equals("String")) { if (type.equals("String")) {
sqlBuilder = sqlBuilder.append("(" + length + ")"); sqlBuilder = sqlBuilder.append("(" + length + ")");
} else if (type.equals("Decimal")) { } else if (type.equals("Decimal")) {
sqlBuilder = sqlBuilder.append("(19,6)"); sqlBuilder = sqlBuilder.append("(19,6)");
} else if (type.equals("Dropdown")) {
sqlBuilder = sqlBuilder.append("(11)");
} }
} }
if (mandatory != null) { if (mandatory != null) {
@@ -317,7 +329,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
sqlBuilder = sqlBuilder.append(" DEFAULT NULL"); sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
} }
} }
sqlBuilder = sqlBuilder.append(", "); sqlBuilder = sqlBuilder.append(", ");
} }
@@ -326,29 +338,29 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
public CommandProcessingResult createDatatable(final JsonCommand command) { public CommandProcessingResult createDatatable(final JsonCommand command) {
String datatableName = null; String datatableName = null;
try { try {
context.authenticatedUser(); context.authenticatedUser();
this.fromApiJsonDeserializer.validateForCreate(command.json()); this.fromApiJsonDeserializer.validateForCreate(command.json());
JsonElement element = this.fromJsonHelper.parse(command.json()); JsonElement element = this.fromJsonHelper.parse(command.json());
JsonArray columns = this.fromJsonHelper.extractJsonArrayNamed("columns", element); JsonArray columns = this.fromJsonHelper.extractJsonArrayNamed("columns", element);
datatableName = this.fromJsonHelper.extractStringNamed("datatableName", element); datatableName = this.fromJsonHelper.extractStringNamed("datatableName", element);
String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element); String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element);
Boolean multiRow = this.fromJsonHelper.extractBooleanNamed("multiRow", element); Boolean multiRow = this.fromJsonHelper.extractBooleanNamed("multiRow", element);
if (multiRow == null) { if (multiRow == null) {
multiRow = false; multiRow = false;
} }
validateDatatableName(datatableName); validateDatatableName(datatableName);
validateAppTable(apptableName); validateAppTable(apptableName);
String fkColumnName = apptableName.substring(2) + "_id"; String fkColumnName = apptableName.substring(2) + "_id";
String fkName = datatableName.toLowerCase().replaceAll("\\s", "_"); String fkName = datatableName.toLowerCase().replaceAll("\\s", "_");
StringBuilder sqlBuilder = new StringBuilder(); StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` ("); sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` (");
if (multiRow) { if (multiRow) {
sqlBuilder = sqlBuilder sqlBuilder = sqlBuilder
.append("`id` BIGINT(20) NOT NULL AUTO_INCREMENT, ") .append("`id` BIGINT(20) NOT NULL AUTO_INCREMENT, ")
@@ -357,14 +369,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
sqlBuilder = sqlBuilder sqlBuilder = sqlBuilder
.append("`" + fkColumnName + "` BIGINT(20) NOT NULL, "); .append("`" + fkColumnName + "` BIGINT(20) NOT NULL, ");
} }
for (JsonElement column : columns) { for (JsonElement column : columns) {
parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder); parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder);
} }
// Remove trailing comma and space // Remove trailing comma and space
sqlBuilder = sqlBuilder.delete(sqlBuilder.length() - 2, sqlBuilder.length()); sqlBuilder = sqlBuilder.delete(sqlBuilder.length() - 2, sqlBuilder.length());
if (multiRow) { if (multiRow) {
sqlBuilder = sqlBuilder sqlBuilder = sqlBuilder
.append(", PRIMARY KEY (`id`)") .append(", PRIMARY KEY (`id`)")
@@ -372,7 +384,6 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
.append(", CONSTRAINT `fk_" + fkName + "` ") .append(", CONSTRAINT `fk_" + fkName + "` ")
.append("FOREIGN KEY (`" + fkColumnName + "`) ") .append("FOREIGN KEY (`" + fkColumnName + "`) ")
.append("REFERENCES `" + apptableName + "` (`id`)"); .append("REFERENCES `" + apptableName + "` (`id`)");
} else { } else {
sqlBuilder = sqlBuilder sqlBuilder = sqlBuilder
.append(", PRIMARY KEY (`" + fkColumnName + "`)") .append(", PRIMARY KEY (`" + fkColumnName + "`)")
@@ -380,10 +391,10 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
.append("FOREIGN KEY (`" + fkColumnName + "`) ") .append("FOREIGN KEY (`" + fkColumnName + "`) ")
.append("REFERENCES `" + apptableName + "` (`id`)"); .append("REFERENCES `" + apptableName + "` (`id`)");
} }
sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;"); sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
this.jdbcTemplate.execute(sqlBuilder.toString()); this.jdbcTemplate.execute(sqlBuilder.toString());
registerDatatable(datatableName, apptableName); registerDatatable(datatableName, apptableName);
} catch (SQLGrammarException e) { } catch (SQLGrammarException e) {
Throwable realCause = e.getCause(); Throwable realCause = e.getCause();
@@ -400,25 +411,43 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
.build(); .build();
} }
private void parseDatatableColumnForUpdate(final JsonObject column, StringBuilder sqlBuilder) { private void parseDatatableColumnForUpdate(final JsonObject column,
final Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition, StringBuilder sqlBuilder) {
String name = (column.has("name")) ? column.get("name").getAsString() : null; String name = (column.has("name")) ? column.get("name").getAsString() : null;
String newName = (column.has("newName")) ? column.get("newName").getAsString() : name; 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; Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
String after = (column.has("after")) ? column.get("after").getAsString() : null; String after = (column.has("after")) ? column.get("after").getAsString() : null;
String code = (column.has("code")) ? column.get("code").getAsString() : null;
String mysqlType = apiTypeToMySQL.get(type); String newCode = (column.has("newCode")) ? column.get("newCode").getAsString() : null;
sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + mysqlType);
if (StringUtils.isNotBlank(code)) {
if (type != null) { name = datatableColumnNameToCodeValueName(name, code);
if (type.equals("String") && length != null) {
sqlBuilder = sqlBuilder.append("(" + length + ")"); if (StringUtils.isNotBlank(newCode)) {
} else if (type.equals("Decimal")) { newName = datatableColumnNameToCodeValueName(newName, newCode);
sqlBuilder = sqlBuilder.append("(19,6)"); } 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 != null) {
if (mandatory) { if (mandatory) {
sqlBuilder = sqlBuilder.append(" NOT NULL"); sqlBuilder = sqlBuilder.append(" NOT NULL");
@@ -432,21 +461,28 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} }
private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder) { private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder) {
String name = (column.has("name")) ? column.get("name").getAsString() : null; String name = (column.has("name")) ? column.get("name").getAsString() : null;
String type = (column.has("type")) ? column.get("type").getAsString() : null; String type = (column.has("type")) ? column.get("type").getAsString() : null;
Integer length = (column.has("length")) ? column.get("length").getAsInt() : null; Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
String after = (column.has("after")) ? column.get("after").getAsString() : null; 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); String mysqlType = apiTypeToMySQL.get(type);
sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType); sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType);
if (type != null) { if (type != null) {
if (type.equals("String") && length != null) { if (type.equals("String") && length != null) {
sqlBuilder = sqlBuilder.append("(" + length + ")"); sqlBuilder = sqlBuilder.append("(" + length + ")");
} else if (type.equals("Decimal")) { } else if (type.equals("Decimal")) {
sqlBuilder = sqlBuilder.append("(19,6)"); sqlBuilder = sqlBuilder.append("(19,6)");
} else if (type.equals("Dropdown")) {
sqlBuilder = sqlBuilder.append("(11)");
} }
} }
if (mandatory != null) { if (mandatory != null) {
@@ -462,9 +498,9 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} }
private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder) { private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder) {
String name = (column.has("name")) ? column.get("name").getAsString() : null; String name = (column.has("name")) ? column.get("name").getAsString() : null;
sqlBuilder = sqlBuilder.append(", DROP COLUMN `" + name + "`"); sqlBuilder = sqlBuilder.append(", DROP COLUMN `" + name + "`");
} }
@@ -475,59 +511,95 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
try { try {
context.authenticatedUser(); context.authenticatedUser();
this.fromApiJsonDeserializer.validateForUpdate(command.json()); this.fromApiJsonDeserializer.validateForUpdate(command.json());
JsonElement element = this.fromJsonHelper.parse(command.json()); JsonElement element = this.fromJsonHelper.parse(command.json());
JsonArray changeColumns = this.fromJsonHelper.extractJsonArrayNamed("changeColumns", element); JsonArray changeColumns = this.fromJsonHelper.extractJsonArrayNamed("changeColumns", element);
JsonArray addColumns = this.fromJsonHelper.extractJsonArrayNamed("addColumns", element); JsonArray addColumns = this.fromJsonHelper.extractJsonArrayNamed("addColumns", element);
JsonArray dropColumns = this.fromJsonHelper.extractJsonArrayNamed("dropColumns", element); JsonArray dropColumns = this.fromJsonHelper.extractJsonArrayNamed("dropColumns", element);
String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element); String apptableName = this.fromJsonHelper.extractStringNamed("apptableName", element);
validateDatatableName(datatableName); validateDatatableName(datatableName);
List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService.fillResultsetColumnHeaders(datatableName);
Map<String, ResultsetColumnHeaderData> mapColumnNameDefinition = new HashMap<String, ResultsetColumnHeaderData>();
for (ResultsetColumnHeaderData columnHeader : columnHeaderData) {
mapColumnNameDefinition.put(columnHeader.getColumnName(), columnHeader);
}
if (!StringUtils.isBlank(apptableName)) { if (!StringUtils.isBlank(apptableName)) {
validateAppTable(apptableName); validateAppTable(apptableName);
String oldApptableName = this.queryForApplicationTableName(datatableName); String oldApptableName = this.queryForApplicationTableName(datatableName);
if (!StringUtils.equals(oldApptableName, apptableName)) { if (!StringUtils.equals(oldApptableName, apptableName)) {
deregisterDatatable(datatableName); deregisterDatatable(datatableName);
registerDatatable(datatableName, apptableName); registerDatatable(datatableName, apptableName);
} }
} }
if (changeColumns == null && addColumns == null && dropColumns == null) { if (changeColumns == null && addColumns == null && dropColumns == null) {
return; 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) { if (dropColumns != null) {
StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
for (JsonElement column : dropColumns) { for (JsonElement column : dropColumns) {
parseDatatableColumnForDrop(column.getAsJsonObject(), sqlBuilder); 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());
} }
if (addColumns != null) {
// Remove the first comma, right after ALTER TABLE `datatable`
int indexOfFirstComma = sqlBuilder.indexOf(","); StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
if (indexOfFirstComma != -1) {
sqlBuilder = sqlBuilder.deleteCharAt(indexOfFirstComma); 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) { } catch (SQLGrammarException e) {
Throwable realCause = e.getCause(); Throwable realCause = e.getCause();
if (realCause.getMessage().contains("Unknown column")) { if (realCause.getMessage().toLowerCase().contains("unknown column")) {
throw new PlatformDataIntegrityException( 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 @Transactional
@Override @Override
public void deleteDatatable(final String datatableName) { public void deleteDatatable(final String datatableName) {
try { try {
context.authenticatedUser(); context.authenticatedUser();
validateDatatableName(datatableName); validateDatatableName(datatableName);
deregisterDatatable(datatableName); deregisterDatatable(datatableName);
String sql = "DROP TABLE `" + datatableName + "`"; String sql = "DROP TABLE `" + datatableName + "`";
this.jdbcTemplate.execute(sql); this.jdbcTemplate.execute(sql);
} catch (SQLGrammarException e) { } catch (SQLGrammarException e) {
Throwable realCause = e.getCause(); Throwable realCause = e.getCause();
if (realCause.getMessage().contains("Unknown table")) { if (realCause.getMessage().contains("Unknown table")) {
throw new PlatformDataIntegrityException( throw new PlatformDataIntegrityException(
"error.msg.datatable.missing", realCause.getMessage()); "error.msg.datatable.missing", realCause.getMessage());