This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new ad2f2862c FINERACT-1747 add datatable query api
ad2f2862c is described below

commit ad2f2862cb08c85021d96e90becbcd47eb511957
Author: Zoltan Nebli <[email protected]>
AuthorDate: Wed Oct 12 23:46:19 2022 +0200

    FINERACT-1747 add datatable query api
---
 .gitignore                                         |   7 +-
 .../dataqueries/api/DatatablesApiResource.java     |  20 ++
 .../data/ResultsetColumnHeaderData.java            |   6 +-
 .../service/ReadWriteNonCoreDataService.java       |   3 +
 .../service/ReadWriteNonCoreDataServiceImpl.java   | 189 +++++++++++++++++-
 .../security/utils/SQLInjectionValidator.java      | 104 ++++------
 .../ReadWriteNonCoreDataServiceImplTest.java       | 102 ++++++++++
 .../security/utils/SQLInjectionValidatorTest.java  | 147 ++++++++++++++
 .../integrationtests/DatatableIntegrationTest.java | 218 +++++++++++++--------
 9 files changed, 639 insertions(+), 157 deletions(-)

diff --git a/.gitignore b/.gitignore
index 991d4b73a..b63dd898e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,4 +24,9 @@ fineract-provider/config/swagger/fineract-input.yaml
 licenses
 **/.asciidoctor/images/*
 **/.asciidoctor/diagram/*
-**/images/diag-*.svg
\ No newline at end of file
+**/images/diag-*.svg
+fineract-avro-schemas/out/
+fineract-client/out/
+fineract-provider/src/main/generated/
+integration-tests/out/
+module/dummy/starter/out/
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
index a3b37dd27..dc5bdb14c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.infrastructure.dataqueries.api;
 
+import com.google.gson.JsonObject;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.enums.ParameterIn;
@@ -218,6 +219,25 @@ public class DatatablesApiResource {
         return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
     }
 
+    @GET
+    @Path("{datatable}/query")
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Query Data Table values", description = "Query 
values from a registered data table.")
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = List.class))) })
+    public String queryValues(@PathParam("datatable") @Parameter(description = 
"datatable") final String datatable,
+            @QueryParam("columnFilter") @Parameter(description = 
"columnFilter") final String columnFilter,
+            @QueryParam("valueFilter") @Parameter(description = "valueFilter") 
final String valueFilter,
+            @QueryParam("resultColumns") @Parameter(description = 
"resultColumns") final String resultColumns,
+            @Context final UriInfo uriInfo) {
+        
this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
+
+        final List<JsonObject> result = 
this.readWriteNonCoreDataService.queryDataTable(datatable, columnFilter, 
valueFilter,
+                resultColumns);
+
+        return 
this.toApiJsonSerializer.serializePretty(ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()),
 result);
+    }
+
     @GET
     @Path("{datatable}/{apptableId}")
     @Consumes({ MediaType.APPLICATION_JSON })
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java
index 7ae84bf38..5d6c6ea9d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/data/ResultsetColumnHeaderData.java
@@ -87,7 +87,7 @@ public final class ResultsetColumnHeaderData implements 
Serializable {
                 displayType = "DECIMAL";
             } else if (isAnyText()) {
                 displayType = "TEXT";
-            } else if (isBit()) {
+            } else if (isBit() || isBoolean()) {
                 displayType = "BOOLEAN";
             } else {
                 throw new 
PlatformDataIntegrityException("error.msg.invalid.lookup.type",
@@ -231,6 +231,10 @@ public final class ResultsetColumnHeaderData implements 
Serializable {
         return "bit".equalsIgnoreCase(this.columnType);
     }
 
+    private boolean isBoolean() {
+        return "boolean".equalsIgnoreCase(this.columnType) || 
"bool".equalsIgnoreCase(this.columnType);
+    }
+
     public String getColumnName() {
         return this.columnName;
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
index 079982bd6..e95061c30 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.infrastructure.dataqueries.service;
 
+import com.google.gson.JsonObject;
 import java.util.List;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -31,6 +32,8 @@ public interface ReadWriteNonCoreDataService {
 
     DatatableData retrieveDatatable(String datatable);
 
+    List<JsonObject> queryDataTable(String datatable, String columnFilter, 
String valueFilter, String resultColumns);
+
     @PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 
'REGISTER_DATATABLE')")
     void registerDatatable(JsonCommand command);
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
index fe60764ba..97cf62aea 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
@@ -18,6 +18,9 @@
  */
 package org.apache.fineract.infrastructure.dataqueries.service;
 
+import static java.util.Arrays.asList;
+import static 
org.apache.fineract.infrastructure.core.data.ApiParameterError.parameterErrorWithValue;
+
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMap;
 import com.google.gson.JsonArray;
@@ -30,12 +33,18 @@ import java.sql.Date;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Timestamp;
+import java.sql.Types;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Stream;
 import javax.persistence.PersistenceException;
@@ -74,6 +83,7 @@ import 
org.apache.fineract.infrastructure.security.service.SqlInjectionPreventer
 import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
 import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
 import org.apache.fineract.useradministration.domain.AppUser;
+import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.dao.DataAccessException;
@@ -104,11 +114,24 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             .put("number", "INT").put("boolean", "BIT").put("decimal", 
"DECIMAL").put("date", "DATE").put("datetime", "DATETIME")
             .put("text", "TEXT").put("dropdown", "INT").build();
     private static final ImmutableMap<String, String> apiTypeToPostgreSQL = 
ImmutableMap.<String, String>builder().put("string", "VARCHAR")
-            .put("number", "INT").put("boolean", "BIT").put("decimal", 
"DECIMAL").put("date", "DATE").put("datetime", "TIMESTAMP")
+            .put("number", "INT").put("boolean", "boolean").put("decimal", 
"DECIMAL").put("date", "DATE").put("datetime", "TIMESTAMP")
             .put("text", "TEXT").put("dropdown", "INT").build();
 
     private static final List<String> stringDataTypes = Arrays.asList("char", 
"varchar", "blob", "text", "tinyblob", "tinytext",
             "mediumblob", "mediumtext", "longblob", "longtext");
+    private static final DateTimeFormatter DATA_TABLE_DATE_FORMAT = 
DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATA_TABLE_DATETIME_FORMAT = 
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    public static final String PG_BOOLEAN_TYPE = "boolean";
+    public static final String PG_BOOL_TYPE = "bool";
+    public static final String PG_INT_TYPE = "integer";
+    public static final String PG_BIGINT_TYPE = "bigint";
+    public static final String PG_DATE_TYPE = "date";
+    public static final String PG_NUMERIC_TYPE = "numeric";
+    public static final String PG_TEXT_TYPE = "text";
+    public static final String PG_VARCHAR_TYPE = "character varying";
+    public static final String BIT_TYPE = "bit";
+    public static final String MYSQL_DATETIME_TYPE = "datetime";
+    public static final String MYSQL_DATE_TYPE = "date";
 
     private final JdbcTemplate jdbcTemplate;
     private final DatabaseTypeResolver databaseTypeResolver;
@@ -160,9 +183,9 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
 
         // PERMITTED datatables
         SQLInjectionValidator.validateSQLInput(datatable);
-        final String sql = "select application_table_name, 
registered_table_name, entity_subtype" + " from x_registered_table "
-                + " where exists" + " (select 'f'" + " from m_appuser_role ur 
" + " join m_role r on r.id = ur.role_id"
-                + " left join m_role_permission rp on rp.role_id = r.id" + " 
left join m_permission p on p.id = rp.permission_id"
+        final String sql = "select application_table_name, 
registered_table_name, entity_subtype from x_registered_table "
+                + " where exists (select 'f' from m_appuser_role ur join 
m_role r on r.id = ur.role_id"
+                + " left join m_role_permission rp on rp.role_id = r.id left 
join m_permission p on p.id = rp.permission_id"
                 + " where ur.appuser_id = ? and registered_table_name=? and 
(p.code in ('ALL_FUNCTIONS', "
                 + "'ALL_FUNCTIONS_READ') or p.code = concat('READ_', 
registered_table_name))) "
                 + " order by application_table_name, registered_table_name";
@@ -183,6 +206,164 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         return datatableData;
     }
 
+    @Override
+    public List<JsonObject> queryDataTable(String datatable, String 
columnFilter, String valueFilter, String resultColumns) {
+        Arrays.asList(datatable, columnFilter, valueFilter, 
resultColumns).forEach(SQLInjectionValidator::validateDynamicQuery);
+
+        List<ResultsetColumnHeaderData> resultsetColumnHeaderData = 
genericDataService.fillResultsetColumnHeaders(datatable);
+        validateRequestParams(columnFilter, valueFilter, resultColumns, 
resultsetColumnHeaderData);
+
+        String sql = "select " + resultColumns + " from " + datatable + " 
where " + columnFilter + " = ?";
+        SqlRowSet rowSet = null;
+        String filterColumnType = 
resultsetColumnHeaderData.stream().filter(column -> 
Objects.equals(columnFilter, column.getColumnName()))
+                
.findFirst().map(ResultsetColumnHeaderData::getColumnType).orElse(columnFilter 
+ " does not exist in datatable");
+        if (databaseTypeResolver.isPostgreSQL()) {
+            rowSet = callFilteredPgSql(sql, valueFilter, filterColumnType);
+        } else if (databaseTypeResolver.isMySQL()) {
+            rowSet = callFilteredMysql(sql, valueFilter, filterColumnType);
+        }
+
+        String[] resultColumnNames = resultColumns.split(",");
+        List<JsonObject> results = new ArrayList<>();
+        while (rowSet.next()) {
+            extractResults(rowSet, resultColumnNames, results);
+        }
+
+        return results;
+    }
+
+    private void extractResults(SqlRowSet rowSet, String[] resultColumnNames, 
List<JsonObject> results) {
+        JsonObject json = new JsonObject();
+        for (String rcn : resultColumnNames) {
+            Object rowValue = rowSet.getObject(rcn);
+            if (rowValue != null) {
+                if (rowValue instanceof Character) {
+                    json.addProperty(rcn, (Character) rowValue);
+                } else if (rowValue instanceof Number) {
+                    json.addProperty(rcn, new BigDecimal(rowValue.toString()));
+                } else if (rowValue instanceof Boolean) {
+                    json.addProperty(rcn, (Boolean) rowValue);
+                } else if (rowValue instanceof LocalDateTime) {
+                    json.addProperty(rcn, 
DATA_TABLE_DATETIME_FORMAT.format((LocalDateTime) rowValue));
+                } else if (rowValue instanceof Timestamp) {
+                    json.addProperty(rcn, 
DATA_TABLE_DATETIME_FORMAT.format(((Timestamp) rowValue).toLocalDateTime()));
+                } else if (rowValue instanceof LocalDate) {
+                    json.addProperty(rcn, 
DATA_TABLE_DATE_FORMAT.format((LocalDate) rowValue));
+                } else if (rowValue instanceof Date) {
+                    json.addProperty(rcn, 
DATA_TABLE_DATE_FORMAT.format(((Date) rowValue).toLocalDate()));
+                } else {
+                    json.addProperty(rcn, rowValue.toString());
+                }
+            }
+        }
+
+        if (json.size() > 0) {
+            results.add(json);
+        }
+    }
+
+    @NotNull
+    private SqlRowSet callFilteredMysql(String sql, String valueFilter, String 
filterColumnType) {
+        Object finalValueFilter = valueFilter;
+        SqlRowSet rowSet;
+        if (BIT_TYPE.equalsIgnoreCase(filterColumnType)) {
+            int[] argType = new int[1];
+            argType[0] = Types.BIT;
+            finalValueFilter = 
BooleanUtils.toString(BooleanUtils.toBooleanObject(valueFilter), "1", "0", 
"null");
+            rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { 
finalValueFilter }, argType);
+        } else if (MYSQL_DATE_TYPE.equalsIgnoreCase(filterColumnType)) {
+            int[] argType = new int[1];
+            argType[0] = Types.DATE;
+            try {
+                rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { 
LocalDate.parse(valueFilter, DATA_TABLE_DATE_FORMAT) }, argType);
+            } catch (DateTimeParseException e) {
+                List<ApiParameterError> paramErrors = new ArrayList<>();
+                paramErrors.add(parameterErrorWithValue("400",
+                        "Unsupported input type for datatable query! Use 
format: 'yyyy-MM-dd'. Column filter: " + filterColumnType,
+                        "valueFilter", valueFilter));
+                throw new PlatformApiDataValidationException(paramErrors, e);
+            }
+        } else if (MYSQL_DATETIME_TYPE.equals(filterColumnType)) {
+            int[] argType = new int[1];
+            argType[0] = Types.TIMESTAMP;
+            try {
+                rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { 
LocalDateTime.parse(valueFilter, DATA_TABLE_DATETIME_FORMAT) },
+                        argType);
+            } catch (DateTimeParseException e) {
+                List<ApiParameterError> paramErrors = new ArrayList<>();
+                paramErrors.add(parameterErrorWithValue("400",
+                        "Unsupported input type for datatable query! Use 
format: 'yyyy-MM-dd HH:mm:ss'. Column filter: " + filterColumnType,
+                        "valueFilter", valueFilter));
+                throw new PlatformApiDataValidationException(paramErrors, e);
+            }
+        } else {
+            rowSet = jdbcTemplate.queryForRowSet(sql, finalValueFilter);
+        }
+        return rowSet;
+    }
+
+    @NotNull
+    private SqlRowSet callFilteredPgSql(String sql, String valueFilter, String 
filterColumnType) {
+        Object finalValueFilter = valueFilter;
+        int[] argType = new int[1];
+        if (BIT_TYPE.equalsIgnoreCase(filterColumnType)) {
+            finalValueFilter = 
BooleanUtils.toString(BooleanUtils.toBooleanObject(valueFilter), "1", "0", 
"null");
+            argType[0] = Types.BIT;
+        } else if (PG_BOOLEAN_TYPE.equalsIgnoreCase(filterColumnType) || 
PG_BOOL_TYPE.equalsIgnoreCase(filterColumnType)) {
+            finalValueFilter = 
BooleanUtils.toString(BooleanUtils.toBooleanObject(valueFilter), "true", 
"false", "null");
+            argType[0] = Types.BOOLEAN;
+        } else if (PG_INT_TYPE.equalsIgnoreCase(filterColumnType)) {
+            argType[0] = Types.INTEGER;
+        } else if (PG_BIGINT_TYPE.equalsIgnoreCase(filterColumnType)) {
+            argType[0] = Types.BIGINT;
+        } else if (PG_DATE_TYPE.equalsIgnoreCase(filterColumnType)) {
+            argType[0] = Types.DATE;
+        } else if (filterColumnType.toLowerCase().contains("timestamp")) {
+            argType[0] = Types.TIMESTAMP;
+        } else if (PG_NUMERIC_TYPE.equalsIgnoreCase(filterColumnType)) {
+            argType[0] = Types.DECIMAL;
+        } else if (PG_TEXT_TYPE.equalsIgnoreCase(filterColumnType) || 
PG_VARCHAR_TYPE.equalsIgnoreCase(filterColumnType)) {
+            argType[0] = Types.VARCHAR;
+        } else {
+            List<ApiParameterError> paramErrors = new ArrayList<>();
+            paramErrors.add(parameterErrorWithValue("400", "Unsupported input 
type for datatable query! Column filter: " + filterColumnType,
+                    "valueFilter", valueFilter));
+            throw new PlatformApiDataValidationException(paramErrors);
+        }
+        return jdbcTemplate.queryForRowSet(sql, new Object[] { 
finalValueFilter }, argType);
+    }
+
+    private static void validateRequestParams(String columnFilter, String 
valueFilter, String resultColumns,
+            List<ResultsetColumnHeaderData> resultsetColumnHeaderData) {
+        List<ApiParameterError> paramErrors = new ArrayList<>();
+        List<String> dataTableColumnNames = 
resultsetColumnHeaderData.stream().map(ResultsetColumnHeaderData::getColumnName).toList();
+        if (columnFilter == null || columnFilter.isEmpty()) {
+            paramErrors.add(parameterErrorWithValue("400", "Column filter is 
empty!", "columnFilter", columnFilter));
+        } else {
+            if (!dataTableColumnNames.contains(columnFilter)) {
+                paramErrors.add(parameterErrorWithValue("400", "Column filter 
not exist in datatable!", "columnFilter", columnFilter));
+            }
+        }
+
+        if (valueFilter == null || valueFilter.isEmpty()) {
+            paramErrors.add(parameterErrorWithValue("400", "Value filter is 
empty!", "valueFilter", valueFilter));
+        }
+
+        if (resultColumns == null || resultColumns.isEmpty()) {
+            paramErrors.add(parameterErrorWithValue("400", "Result columns 
filter is empty!", "resultColumns", resultColumns));
+        } else {
+            asList(resultColumns.split(",")).forEach(rcn -> {
+                if (!dataTableColumnNames.contains(rcn)) {
+                    paramErrors.add(parameterErrorWithValue("400", "Result 
column not exist in datatable!", "resultColumns", rcn));
+                }
+            });
+        }
+
+        if (!paramErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(paramErrors);
+        }
+    }
+
     private void logAsErrorUnexpectedDataIntegrityException(final Exception 
dve) {
         LOG.error("Error occurred.", dve);
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
index a8128a485..a89c9f16a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
@@ -35,7 +35,7 @@ public final class SQLInjectionValidator {
 
     private static final String[] COMMENTS = { "--", "({", "/*", "#" };
 
-    private static final String SQL_PATTERN = "[a-zA-Z_=,\\-'!><.?\"`% 
()0-9*\n\r]*";
+    private static final String SQL_PATTERN = "[a-zA-Z_=,\\-:'!><.?\"`% 
()0-9*\n\r]*";
 
     public static void validateSQLInput(final String sqlSearch) {
         if (StringUtils.isBlank(sqlSearch)) {
@@ -60,99 +60,72 @@ public final class SQLInjectionValidator {
             }
         }
 
-        // Removing the space before and after '=' operator
-        // String s = " \" OR 1 = 1"; For the cases like this
-        boolean injectionFound = false;
-        String inputSqlString = lowerCaseSQL;
-        while (inputSqlString.indexOf(" =") > 0) { // Don't remove space before
-                                                   // = operator
-            inputSqlString = inputSqlString.replaceAll(" =", "=");
-        }
+        patternMatchSqlInjection(sqlSearch, lowerCaseSQL);
+    }
 
-        while (inputSqlString.indexOf("= ") > 0) { // Don't remove space after 
=
-                                                   // operator
-            inputSqlString = inputSqlString.replaceAll("= ", "=");
+    public static void validateAdhocQuery(final String sqlSearch) {
+        if (StringUtils.isBlank(sqlSearch)) {
+            return;
         }
-
-        StringTokenizer tokenizer = new StringTokenizer(inputSqlString, " ");
-        while (tokenizer.hasMoreTokens()) {
-            String token = tokenizer.nextToken().trim();
-            if (token.equals("'")) {
-                if (tokenizer.hasMoreElements()) {
-                    String nextToken = tokenizer.nextToken().trim();
-                    if (!nextToken.equals("'")) {
-                        injectionFound = true;
-                        break;
-                    }
-                } else {
-                    injectionFound = true;
-                    break;
-                }
-            }
-            if (token.equals("\"")) {
-                if (tokenizer.hasMoreElements()) {
-                    String nextToken = tokenizer.nextToken().trim();
-                    if (!nextToken.equals("\"")) {
-                        injectionFound = true;
-                        break;
-                    }
-                } else {
-                    injectionFound = true;
-                    break;
-                }
-            } else if (token.indexOf('=') > 0) {
-                StringTokenizer operatorToken = new StringTokenizer(token, 
"=");
-                String operand = operatorToken.nextToken().trim();
-                if (!operatorToken.hasMoreTokens()) {
-                    injectionFound = true;
-                    break;
-                }
-                String value = operatorToken.nextToken().trim();
-                if (operand.equals(value)) {
-                    injectionFound = true;
-                    break;
-                }
+        String lowerCaseSQL = sqlSearch.toLowerCase().trim();
+        for (String ddl : DDL_COMMANDS) {
+            if (lowerCaseSQL.startsWith(ddl)) {
+                throw new SQLInjectionException();
             }
         }
-        if (injectionFound) {
-            throw new SQLInjectionException();
-        }
 
-        Pattern pattern = Pattern.compile(SQL_PATTERN);
-        Matcher matcher = pattern.matcher(sqlSearch);
-        if (!matcher.matches()) {
-            throw new SQLInjectionException();
+        for (String comments : COMMENTS) {
+            if (lowerCaseSQL.contains(comments)) {
+                throw new SQLInjectionException();
+            }
         }
+
+        // Removing the space before and after '=' operator
+        // String s = " \" OR 1 = 1"; For the cases like this
+        patternMatchSqlInjection(sqlSearch, lowerCaseSQL);
     }
 
-    public static void validateAdhocQuery(final String sqlSearch) {
+    public static void validateDynamicQuery(final String sqlSearch) {
         if (StringUtils.isBlank(sqlSearch)) {
             return;
         }
-        String lowerCaseSQL = sqlSearch.toLowerCase().trim();
+
+        String lowerCaseSQL = sqlSearch.toLowerCase();
         for (String ddl : DDL_COMMANDS) {
-            if (lowerCaseSQL.startsWith(ddl)) {
+            if (ddl.equals(lowerCaseSQL)) {
                 throw new SQLInjectionException();
             }
         }
 
-        for (String comments : COMMENTS) {
-            if (lowerCaseSQL.contains(comments)) {
+        for (String dml : DML_COMMANDS) {
+            if (dml.equals(lowerCaseSQL)) {
+                throw new SQLInjectionException();
+            }
+        }
+
+        for (String comment : COMMENTS) {
+            if (comment.equals(lowerCaseSQL)) {
                 throw new SQLInjectionException();
             }
         }
 
+        // Removing the space before and after '=' operator
+        // String s = " \" OR 1 = 1"; For the cases like this
+        patternMatchSqlInjection(sqlSearch, lowerCaseSQL);
+    }
+
+    private static void patternMatchSqlInjection(String sqlSearch, String 
lowerCaseSQL) {
         // Removing the space before and after '=' operator
         // String s = " \" OR 1 = 1"; For the cases like this
         boolean injectionFound = false;
         String inputSqlString = lowerCaseSQL;
         while (inputSqlString.indexOf(" =") > 0) { // Don't remove space before
-                                                   // = operator
+            // = operator
             inputSqlString = inputSqlString.replaceAll(" =", "=");
         }
 
         while (inputSqlString.indexOf("= ") > 0) { // Don't remove space after 
=
-                                                   // operator
+            // operator
             inputSqlString = inputSqlString.replaceAll("= ", "=");
         }
 
@@ -206,4 +179,5 @@ public final class SQLInjectionValidator {
             throw new SQLInjectionException();
         }
     }
+
 }
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java
new file mode 100644
index 000000000..caa3ab3b4
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImplTest.java
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.dataqueries.service;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import com.google.gson.JsonObject;
+import java.util.Collections;
+import java.util.List;
+import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import 
org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
+import 
org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
+import org.apache.fineract.infrastructure.security.utils.SQLInjectionException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+
+public class ReadWriteNonCoreDataServiceImplTest {
+
+    @Mock
+    private JdbcTemplate jdbcTemplate;
+
+    @Mock
+    private GenericDataService genericDataService;
+
+    @Mock
+    private DatabaseTypeResolver databaseTypeResolver;
+
+    @InjectMocks
+    private ReadWriteNonCoreDataServiceImpl underTest;
+
+    @BeforeEach
+    public void setup() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    @Test
+    public void testSqlInjectionCaughtQueryDataTable() {
+        assertThrows(SQLInjectionException.class, () -> {
+            underTest.queryDataTable("table", "cf1", "vf1", "' or 1=1");
+        });
+    }
+
+    @Test
+    public void testSqlInjectionCaughtQueryDataTable2() {
+        assertThrows(SQLInjectionException.class, () -> {
+            underTest.queryDataTable("table", "cf1", "vf1", "1; DROP TABLE 
m_loan; SELECT");
+        });
+    }
+
+    @Test
+    public void testQueryDataTableSuccess() {
+        SqlRowSet sqlRS = Mockito.mock(SqlRowSet.class);
+        when(jdbcTemplate.queryForRowSet(eq("select rc1,rc2 from table where 
cf1 = ?"), any(Object[].class), any(int[].class)))
+                .thenReturn(sqlRS);
+        when(sqlRS.next()).thenReturn(true).thenReturn(false);
+        
when(sqlRS.getObject(ArgumentMatchers.anyString())).thenReturn("value1").thenReturn("value2");
+        when(databaseTypeResolver.isPostgreSQL()).thenReturn(true);
+
+        ResultsetColumnHeaderData cf1 = 
ResultsetColumnHeaderData.detailed("cf1", "text", 10L, false, false, null, 
null);
+        ResultsetColumnHeaderData rc1 = 
ResultsetColumnHeaderData.detailed("rc1", "text", 10L, false, false, null, 
null);
+        ResultsetColumnHeaderData rc2 = 
ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, null, 
null);
+        
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1,
 rc1, rc2));
+
+        List<JsonObject> results = underTest.queryDataTable("table", "cf1", 
"vf1", "rc1,rc2");
+
+        Assertions.assertEquals("value1", 
results.get(0).get("rc1").getAsString());
+        Assertions.assertEquals("value2", 
results.get(0).get("rc2").getAsString());
+    }
+
+    @Test
+    public void testQueryDataTableValidationError() {
+        
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(Collections.emptyList());
+        assertThrows(PlatformApiDataValidationException.class, () -> 
underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2"));
+    }
+}
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java
new file mode 100644
index 000000000..a76e2ee07
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidatorTest.java
@@ -0,0 +1,147 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.security.utils;
+
+import java.util.Arrays;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class SQLInjectionValidatorTest {
+
+    private static final String[] DDL_COMMANDS = { "create", "drop", "alter", 
"truncate", "comment", "sleep" };
+    private static final String[] DML_COMMANDS = { "select", "insert", 
"update", "delete", "merge", "upsert", "call" };
+    private static final String[] COMMENTS = { "--", "({", "/*", "#" };
+
+    @Test
+    public void testValidateSqlInputQuote() {
+        Assertions.assertThrows(SQLInjectionException.class, () -> {
+            SQLInjectionValidator.validateSQLInput("' or 1=1");
+        });
+    }
+
+    @Test
+    public void testValidateSqlInputSemicolon() {
+        Assertions.assertThrows(SQLInjectionException.class, () -> {
+            SQLInjectionValidator.validateSQLInput("; drop table foo;");
+        });
+    }
+
+    @Test
+    public void testValidateAdhocQueryQuote() {
+        Assertions.assertThrows(SQLInjectionException.class, () -> {
+            SQLInjectionValidator.validateAdhocQuery("' or 1=1");
+        });
+    }
+
+    @Test
+    public void testValidateAdhocQuerySemicolon() {
+        Assertions.assertThrows(SQLInjectionException.class, () -> {
+            SQLInjectionValidator.validateAdhocQuery("; drop table foo;");
+        });
+    }
+
+    @Test
+    public void testValidateDynamicQueryQuote() {
+        Assertions.assertThrows(SQLInjectionException.class, () -> {
+            SQLInjectionValidator.validateDynamicQuery("' or 1=1");
+        });
+    }
+
+    @Test
+    public void testValidateDynamicQuerySemicolon() {
+        Assertions.assertThrows(SQLInjectionException.class, () -> {
+            SQLInjectionValidator.validateDynamicQuery("; drop table foo;");
+        });
+    }
+
+    @Test
+    public void testValidateSqlLInputReservedWords() {
+        Arrays.asList(DDL_COMMANDS).forEach(ddl -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateSQLInput(ddl);
+            });
+        });
+
+        Arrays.asList(DML_COMMANDS).forEach(dml -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateSQLInput(dml);
+            });
+        });
+
+        Arrays.asList(COMMENTS).forEach(comment -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateSQLInput(comment);
+            });
+        });
+    }
+
+    @Test
+    public void testValidateAdhocQueryReservedWords() {
+        Arrays.asList(DDL_COMMANDS).forEach(ddl -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateAdhocQuery(ddl);
+            });
+        });
+
+        // left out intentionally from adhocquery validation?
+        // Arrays.asList(DML_COMMANDS).forEach(dml -> {
+        // Assertions.assertThrows(SQLInjectionException.class, () -> {
+        // SQLInjectionValidator.validateAdhocQuery(dml);
+        // });
+        // });
+
+        Arrays.asList(COMMENTS).forEach(comment -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateAdhocQuery(comment);
+            });
+        });
+    }
+
+    @Test
+    public void testValidateDynamicQueryReservedWords() {
+        Arrays.asList(DDL_COMMANDS).forEach(ddl -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateDynamicQuery(ddl);
+            });
+        });
+
+        Arrays.asList(DML_COMMANDS).forEach(dml -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateDynamicQuery(dml);
+            });
+        });
+
+        Arrays.asList(COMMENTS).forEach(comment -> {
+            Assertions.assertThrows(SQLInjectionException.class, () -> {
+                SQLInjectionValidator.validateDynamicQuery(comment);
+            });
+        });
+    }
+
+    @Test
+    public void testValidateDynamicQueryColon() {
+        SQLInjectionValidator.validateDynamicQuery("2022-10-13 18:40:21");
+    }
+
+    @Test
+    public void testValidateDynamicQueryReservedWordsInsideValue() {
+        Arrays.asList(DDL_COMMANDS).forEach(ddl -> 
SQLInjectionValidator.validateDynamicQuery("foo" + ddl));
+        Arrays.asList(DML_COMMANDS).forEach(dml -> 
SQLInjectionValidator.validateDynamicQuery("foo" + dml));
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DatatableIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DatatableIntegrationTest.java
index 4019b06fb..eed138d0e 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DatatableIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DatatableIntegrationTest.java
@@ -19,20 +19,30 @@
 package org.apache.fineract.integrationtests;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
 import io.restassured.builder.RequestSpecBuilder;
 import io.restassured.builder.ResponseSpecBuilder;
 import io.restassured.http.ContentType;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.fineract.client.util.Calls;
+import org.apache.fineract.integrationtests.client.IntegrationTest;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.Utils;
 import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
@@ -45,7 +55,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class DatatableIntegrationTest {
+public class DatatableIntegrationTest extends IntegrationTest {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(DatatableIntegrationTest.class);
 
@@ -80,15 +90,16 @@ public class DatatableIntegrationTest {
     }
 
     @Test
-    public void validateCreateReadDeleteDatatable() {
+    public void validateCreateReadDeleteDatatable() throws ParseException {
         // Fetch / Create tst code
-        HashMap<String, Object> codeResponse = 
CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, "TST_TST_TST");
+        String tst_tst_tst = "TST_TST_TST".toLowerCase();
+        HashMap<String, Object> codeResponse = 
CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, tst_tst_tst);
 
         Integer createdCodeId = (Integer) codeResponse.get("id");
         Integer createdCodeValueId;
         Integer createdCodeValueIdSecond;
         if (createdCodeId == null) {
-            createdCodeId = (Integer) CodeHelper.createCode(this.requestSpec, 
this.responseSpec, "TST_TST_TST", "resourceId");
+            createdCodeId = (Integer) CodeHelper.createCode(this.requestSpec, 
this.responseSpec, tst_tst_tst, "resourceId");
 
             createdCodeValueId = CodeHelper.createCodeValue(this.requestSpec, 
this.responseSpec, createdCodeId,
                     Utils.randomStringGenerator("cv_", 8), 1);
@@ -104,18 +115,29 @@ public class DatatableIntegrationTest {
         // creating datatable for client entity
         final HashMap<String, Object> columnMap = new HashMap<>();
         final List<HashMap<String, Object>> datatableColumnsList = new 
ArrayList<>();
-        columnMap.put("datatableName", 
Utils.randomNameGenerator(CLIENT_APP_TABLE_NAME + "_", 5));
+        columnMap.put("datatableName", 
Utils.randomNameGenerator(CLIENT_APP_TABLE_NAME + "_", 
5).toLowerCase().toLowerCase());
         columnMap.put("apptableName", CLIENT_APP_TABLE_NAME);
         columnMap.put("entitySubType", "PERSON");
         columnMap.put("multiRow", false);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsABoolean", "Boolean", false, null, null);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, "itsADate", 
"Date", true, null, null);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsADatetime", "Datetime", true, null, null);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsADecimal", "Decimal", true, null, null);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsADropdown", "Dropdown", false, null, "TST_TST_TST");
-        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsANumber", "Number", true, null, null);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsAString", "String", true, 10, null);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, "itsAText", 
"Text", true, null, null);
+        String itsABoolean = "itsaboolean";
+        String itsADate = "itsadate";
+        String itsADatetime = "itsadatetime";
+        String itsADecimal = "itsadecimal";
+        String itsADropdown = "itsadropdown";
+        String itsANumber = "itsanumber";
+        String itsAString = "itsastring";
+        String itsAText = "itsatext";
+        String tst_tst_tst_cd_itsADropdown = tst_tst_tst + "_cd_itsadropdown";
+        String dateFormat = "dateFormat";
+
+        DatatableHelper.addDatatableColumns(datatableColumnsList, itsABoolean, 
"Boolean", false, null, null);
+        DatatableHelper.addDatatableColumns(datatableColumnsList, itsADate, 
"Date", true, null, null);
+        DatatableHelper.addDatatableColumns(datatableColumnsList, 
itsADatetime, "Datetime", true, null, null);
+        DatatableHelper.addDatatableColumns(datatableColumnsList, itsADecimal, 
"Decimal", true, null, null);
+        DatatableHelper.addDatatableColumns(datatableColumnsList, 
itsADropdown, "Dropdown", false, null, tst_tst_tst);
+        DatatableHelper.addDatatableColumns(datatableColumnsList, itsANumber, 
"Number", true, null, null);
+        DatatableHelper.addDatatableColumns(datatableColumnsList, itsAString, 
"String", true, 10, null);
+        DatatableHelper.addDatatableColumns(datatableColumnsList, itsAText, 
"Text", true, null, null);
         columnMap.put("columns", datatableColumnsList);
         String datatabelRequestJsonString = new Gson().toJson(columnMap);
         LOG.info("map : {}", datatabelRequestJsonString);
@@ -138,17 +160,16 @@ public class DatatableIntegrationTest {
         final boolean genericResultSet = true;
 
         final HashMap<String, Object> datatableEntryMap = new HashMap<>();
-        datatableEntryMap.put("itsABoolean", Utils.randomNumberGenerator(1) % 
2 == 0);
-        datatableEntryMap.put("itsADate", 
Utils.randomDateGenerator("yyyy-MM-dd"));
-        datatableEntryMap.put("itsADatetime", 
Utils.randomDateTimeGenerator("yyyy-MM-dd"));
-        datatableEntryMap.put("itsADecimal", Utils.randomDecimalGenerator(4, 
3));
-        datatableEntryMap.put("TST_TST_TST_cd_itsADropdown", 
createdCodeValueId);
-        datatableEntryMap.put("itsANumber", Utils.randomNumberGenerator(5));
-        datatableEntryMap.put("itsAString", Utils.randomStringGenerator("", 
8));
-        datatableEntryMap.put("itsAText", Utils.randomStringGenerator("", 
1000));
-
+        datatableEntryMap.put(itsABoolean, Utils.randomNumberGenerator(1) % 2 
== 0);
+        datatableEntryMap.put(itsADate, 
Utils.randomDateGenerator("yyyy-MM-dd"));
+        datatableEntryMap.put(itsADatetime, 
Utils.randomDateTimeGenerator("yyyy-MM-dd"));
+        datatableEntryMap.put(itsADecimal, Utils.randomDecimalGenerator(4, 3));
+        datatableEntryMap.put(tst_tst_tst_cd_itsADropdown, createdCodeValueId);
+        datatableEntryMap.put(itsANumber, Utils.randomNumberGenerator(5));
+        datatableEntryMap.put(itsAString, Utils.randomStringGenerator("", 8));
+        datatableEntryMap.put(itsAText, Utils.randomStringGenerator("", 1000));
         datatableEntryMap.put("locale", "en");
-        datatableEntryMap.put("dateFormat", "yyyy-MM-dd");
+        datatableEntryMap.put(dateFormat, "yyyy-MM-dd");
 
         String datatabelEntryRequestJsonString = new 
Gson().toJson(datatableEntryMap);
         LOG.info("map : {}", datatabelEntryRequestJsonString);
@@ -166,32 +187,32 @@ public class DatatableIntegrationTest {
         assertEquals("client_id", ((Map) ((List) 
items.get("columnHeaders")).get(0)).get("columnName"));
         assertEquals(clientID, ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(0));
 
-        assertEquals("itsABoolean", ((Map) ((List) 
items.get("columnHeaders")).get(1)).get("columnName"));
-        assertEquals(datatableEntryMap.get("itsABoolean"), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(1));
+        assertEquals(itsABoolean, ((Map) ((List) 
items.get("columnHeaders")).get(1)).get("columnName"));
+        assertEquals(datatableEntryMap.get(itsABoolean), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(1));
 
-        assertEquals("itsADate", ((Map) ((List) 
items.get("columnHeaders")).get(2)).get("columnName"));
-        assertEquals(datatableEntryMap.get("itsADate"),
+        assertEquals(itsADate, ((Map) ((List) 
items.get("columnHeaders")).get(2)).get("columnName"));
+        assertEquals(datatableEntryMap.get(itsADate),
                 Utils.arrayDateToString((List) ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(2)));
 
-        assertEquals("itsADatetime", ((Map) ((List) 
items.get("columnHeaders")).get(3)).get("columnName"));
-        assertEquals(datatableEntryMap.get("itsADatetime"),
+        assertEquals(itsADatetime, ((Map) ((List) 
items.get("columnHeaders")).get(3)).get("columnName"));
+        assertEquals(datatableEntryMap.get(itsADatetime),
                 Utils.arrayDateTimeToString((List) ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(3)));
 
-        assertEquals("itsADecimal", ((Map) ((List) 
items.get("columnHeaders")).get(4)).get("columnName"));
-        assertEquals(datatableEntryMap.get("itsADecimal"), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(4));
+        assertEquals(itsADecimal, ((Map) ((List) 
items.get("columnHeaders")).get(4)).get("columnName"));
+        assertEquals(datatableEntryMap.get(itsADecimal), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(4));
 
-        assertEquals("TST_TST_TST_cd_itsADropdown", ((Map) ((List) 
items.get("columnHeaders")).get(5)).get("columnName"));
-        assertEquals(datatableEntryMap.get("TST_TST_TST_cd_itsADropdown"),
+        assertEquals(tst_tst_tst_cd_itsADropdown, ((Map) ((List) 
items.get("columnHeaders")).get(5)).get("columnName"));
+        assertEquals(datatableEntryMap.get(tst_tst_tst_cd_itsADropdown),
                 ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(5));
 
-        assertEquals("itsANumber", ((Map) ((List) 
items.get("columnHeaders")).get(6)).get("columnName"));
-        assertEquals(datatableEntryMap.get("itsANumber"), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(6));
+        assertEquals(itsANumber, ((Map) ((List) 
items.get("columnHeaders")).get(6)).get("columnName"));
+        assertEquals(datatableEntryMap.get(itsANumber), ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(6));
 
-        assertEquals("itsAString", ((Map) ((List) 
items.get("columnHeaders")).get(7)).get("columnName"));
-        assertEquals(datatableEntryMap.get("itsAString"), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(7));
+        assertEquals(itsAString, ((Map) ((List) 
items.get("columnHeaders")).get(7)).get("columnName"));
+        assertEquals(datatableEntryMap.get(itsAString), ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(7));
 
-        assertEquals("itsAText", ((Map) ((List) 
items.get("columnHeaders")).get(8)).get("columnName"));
-        assertEquals(datatableEntryMap.get("itsAText"), ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(8));
+        assertEquals(itsAText, ((Map) ((List) 
items.get("columnHeaders")).get(8)).get("columnName"));
+        assertEquals(datatableEntryMap.get(itsAText), ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(8));
 
         // Read the Datatable entry generated with genericResultSet in false
         List<HashMap<String, Object>> datatableEntryResponseNoGenericResult = 
this.datatableHelper.readDatatableEntry(datatableName,
@@ -200,32 +221,32 @@ public class DatatableIntegrationTest {
         assertEquals(1, datatableEntryResponseNoGenericResult.size());
 
         assertEquals(clientID, 
datatableEntryResponseNoGenericResult.get(0).get("client_id"));
-        assertEquals(datatableEntryMap.get("itsABoolean"),
-                Boolean.valueOf((String) 
datatableEntryResponseNoGenericResult.get(0).get("itsABoolean")));
-        assertEquals(datatableEntryMap.get("itsADate"),
-                Utils.arrayDateToString((List) 
datatableEntryResponseNoGenericResult.get(0).get("itsADate")));
-        assertEquals(datatableEntryMap.get("itsADecimal"), 
datatableEntryResponseNoGenericResult.get(0).get("itsADecimal"));
-        assertEquals(datatableEntryMap.get("itsADatetime"),
-                Utils.arrayDateTimeToString((List<Integer>) 
datatableEntryResponseNoGenericResult.get(0).get("itsADatetime")));
-        assertEquals(datatableEntryMap.get("TST_TST_TST_cd_itsADropdown"),
-                
datatableEntryResponseNoGenericResult.get(0).get("TST_TST_TST_cd_itsADropdown"));
-        assertEquals(datatableEntryMap.get("itsANumber"), 
datatableEntryResponseNoGenericResult.get(0).get("itsANumber"));
-        assertEquals(datatableEntryMap.get("itsAString"), 
datatableEntryResponseNoGenericResult.get(0).get("itsAString"));
-        assertEquals(datatableEntryMap.get("itsAText"), 
datatableEntryResponseNoGenericResult.get(0).get("itsAText"));
+        assertEquals(datatableEntryMap.get(itsABoolean),
+                Boolean.valueOf((String) 
datatableEntryResponseNoGenericResult.get(0).get(itsABoolean)));
+        assertEquals(datatableEntryMap.get(itsADate),
+                Utils.arrayDateToString((List) 
datatableEntryResponseNoGenericResult.get(0).get(itsADate)));
+        assertEquals(datatableEntryMap.get(itsADecimal), 
datatableEntryResponseNoGenericResult.get(0).get(itsADecimal));
+        assertEquals(datatableEntryMap.get(itsADatetime),
+                Utils.arrayDateTimeToString((List<Integer>) 
datatableEntryResponseNoGenericResult.get(0).get(itsADatetime)));
+        assertEquals(datatableEntryMap.get(tst_tst_tst_cd_itsADropdown),
+                
datatableEntryResponseNoGenericResult.get(0).get(tst_tst_tst_cd_itsADropdown));
+        assertEquals(datatableEntryMap.get(itsANumber), 
datatableEntryResponseNoGenericResult.get(0).get(itsANumber));
+        assertEquals(datatableEntryMap.get(itsAString), 
datatableEntryResponseNoGenericResult.get(0).get(itsAString));
+        assertEquals(datatableEntryMap.get(itsAText), 
datatableEntryResponseNoGenericResult.get(0).get(itsAText));
 
         // Update datatable entry
-        Boolean previousBoolean = (Boolean) 
datatableEntryMap.get("itsABoolean");
-        datatableEntryMap.put("itsABoolean", !previousBoolean);
-        datatableEntryMap.put("itsADate", 
Utils.randomDateGenerator("yyyy-MM-dd"));
-        datatableEntryMap.put("itsADatetime", 
Utils.randomDateTimeGenerator("yyyy-MM-dd"));
-        datatableEntryMap.put("itsADecimal", Utils.randomDecimalGenerator(4, 
3));
-        datatableEntryMap.put("TST_TST_TST_cd_itsADropdown", null);
-        datatableEntryMap.put("itsANumber", Utils.randomNumberGenerator(5));
-        datatableEntryMap.put("itsAString", Utils.randomStringGenerator("", 
8));
-        datatableEntryMap.put("itsAText", Utils.randomStringGenerator("", 
1000));
+        Boolean previousBoolean = (Boolean) datatableEntryMap.get(itsABoolean);
+        datatableEntryMap.put(itsABoolean, !previousBoolean);
+        datatableEntryMap.put(itsADate, 
Utils.randomDateGenerator("yyyy-MM-dd"));
+        datatableEntryMap.put(itsADatetime, 
Utils.randomDateTimeGenerator("yyyy-MM-dd"));
+        datatableEntryMap.put(itsADecimal, Utils.randomDecimalGenerator(4, 3));
+        datatableEntryMap.put(tst_tst_tst_cd_itsADropdown, null);
+        datatableEntryMap.put(itsANumber, Utils.randomNumberGenerator(5));
+        datatableEntryMap.put(itsAString, Utils.randomStringGenerator("", 8));
+        datatableEntryMap.put(itsAText, Utils.randomStringGenerator("", 1000));
 
         datatableEntryMap.put("locale", "en");
-        datatableEntryMap.put("dateFormat", "yyyy-MM-dd");
+        datatableEntryMap.put(dateFormat, "yyyy-MM-dd");
 
         datatabelEntryRequestJsonString = new Gson().toJson(datatableEntryMap);
         LOG.info("map : {}", datatabelEntryRequestJsonString);
@@ -235,17 +256,41 @@ public class DatatableIntegrationTest {
 
         assertEquals(clientID, updatedDatatableEntryResponse.get("clientId"));
 
-        assertEquals(datatableEntryMap.get("itsABoolean"), ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsABoolean"));
-        assertEquals(datatableEntryMap.get("itsADate"),
-                Utils.arrayDateToString((List) ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsADate")));
-        assertEquals(datatableEntryMap.get("itsADecimal"), ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsADecimal"));
-        assertEquals(datatableEntryMap.get("itsADatetime"),
-                Utils.arrayDateTimeToString((List<Integer>) ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsADatetime")));
-        assertEquals(datatableEntryMap.get("TST_TST_TST_cd_itsADropdown"),
-                ((Map) 
updatedDatatableEntryResponse.get("changes")).get("TST_TST_TST_cd_itsADropdown"));
-        assertEquals(datatableEntryMap.get("itsANumber"), ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsANumber"));
-        assertEquals(datatableEntryMap.get("itsAString"), ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsAString"));
-        assertEquals(datatableEntryMap.get("itsAText"), ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsAText"));
+        assertEquals(datatableEntryMap.get(itsABoolean), ((Map) 
updatedDatatableEntryResponse.get("changes")).get(itsABoolean));
+        assertEquals(datatableEntryMap.get(itsADate),
+                Utils.arrayDateToString((List) ((Map) 
updatedDatatableEntryResponse.get("changes")).get(itsADate)));
+        assertEquals(datatableEntryMap.get(itsADecimal), ((Map) 
updatedDatatableEntryResponse.get("changes")).get(itsADecimal));
+        assertEquals(datatableEntryMap.get(itsADatetime),
+                Utils.arrayDateTimeToString((List<Integer>) ((Map) 
updatedDatatableEntryResponse.get("changes")).get(itsADatetime)));
+        assertEquals(datatableEntryMap.get(tst_tst_tst_cd_itsADropdown),
+                ((Map) 
updatedDatatableEntryResponse.get("changes")).get(tst_tst_tst_cd_itsADropdown));
+        assertEquals(datatableEntryMap.get(itsANumber), ((Map) 
updatedDatatableEntryResponse.get("changes")).get(itsANumber));
+        assertEquals(datatableEntryMap.get(itsAString), ((Map) 
updatedDatatableEntryResponse.get("changes")).get(itsAString));
+        assertEquals(datatableEntryMap.get(itsAText), ((Map) 
updatedDatatableEntryResponse.get("changes")).get(itsAText));
+
+        List<String> columnsToValidate = List.of(itsABoolean, itsADate, 
itsADatetime, itsAString, itsAText, itsADecimal,
+                tst_tst_tst_cd_itsADropdown);
+        for (String column : columnsToValidate) {
+            String valueFilter = column.equals(tst_tst_tst_cd_itsADropdown) ? 
createdCodeValueId.toString()
+                    : datatableEntryMap.get(column).toString();
+            String rows = 
Calls.ok(fineract().dataTables.queryValues(datatableName, column, valueFilter, 
column));
+            JsonArray jsonArray = 
JsonParser.parseString(rows).getAsJsonArray();
+            if (itsADatetime.equals(column)) {
+                DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                Date parsedRequest = 
df1.parse(datatableEntryMap.get(column).toString());
+                Date parsedResponse = 
df2.parse(jsonArray.get(0).getAsJsonObject().get(column).getAsString());
+                assertFalse(parsedRequest.after(parsedResponse));
+                assertFalse(parsedRequest.before(parsedResponse));
+            } else if (itsADecimal.equals(column)) {
+                assertEquals(0, new 
BigDecimal(datatableEntryMap.get(column).toString())
+                        .compareTo(new 
BigDecimal(jsonArray.get(0).getAsJsonObject().get(column).getAsString())));
+            } else if (tst_tst_tst_cd_itsADropdown.equals(column)) {
+                assertEquals(createdCodeValueId.toString(), 
jsonArray.get(0).getAsJsonObject().get(column).getAsString());
+            } else {
+                assertEquals(datatableEntryMap.get(column).toString(), 
jsonArray.get(0).getAsJsonObject().get(column).getAsString());
+            }
+        }
 
         // deleting datatable entries
         Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(datatableName, clientID, 
"clientId");
@@ -383,13 +428,14 @@ public class DatatableIntegrationTest {
     @Test
     public void validateReadDatatableMultirow() {
         // Fetch / Create TST code
-        HashMap<String, Object> codeResponse = 
CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, "TST_TST_TST");
+        String tst_tst_tst = "tst_tst_tst";
+        HashMap<String, Object> codeResponse = 
CodeHelper.getCodeByName(this.requestSpec, this.responseSpec, tst_tst_tst);
 
         Integer createdCodeId = (Integer) codeResponse.get("id");
         Integer createdCodeValueId;
         Integer createdCodeValueIdSecond;
         if (createdCodeId == null) {
-            createdCodeId = (Integer) CodeHelper.createCode(this.requestSpec, 
this.responseSpec, "TST_TST_TST", "resourceId");
+            createdCodeId = (Integer) CodeHelper.createCode(this.requestSpec, 
this.responseSpec, tst_tst_tst, "resourceId");
 
             createdCodeValueId = CodeHelper.createCodeValue(this.requestSpec, 
this.responseSpec, createdCodeId,
                     Utils.randomStringGenerator("cv_", 8), 1);
@@ -413,7 +459,7 @@ public class DatatableIntegrationTest {
         DatatableHelper.addDatatableColumns(datatableColumnsList, "itsADate", 
"Date", false, null, null);
         DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsADatetime", "Datetime", false, null, null);
         DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsADecimal", "Decimal", false, null, null);
-        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsADropdown", "Dropdown", false, null, "TST_TST_TST");
+        DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsADropdown", "Dropdown", false, null, tst_tst_tst);
         DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsANumber", "Number", false, null, null);
         DatatableHelper.addDatatableColumns(datatableColumnsList, 
"itsAString", "String", false, 10, null);
         DatatableHelper.addDatatableColumns(datatableColumnsList, "itsAText", 
"Text", false, null, null);
@@ -445,7 +491,7 @@ public class DatatableIntegrationTest {
         datatableEntryMap.put("itsADate", 
Utils.randomDateGenerator("yyyy-MM-dd"));
         datatableEntryMap.put("itsADatetime", 
Utils.randomDateTimeGenerator("yyyy-MM-dd"));
         datatableEntryMap.put("itsADecimal", Utils.randomDecimalGenerator(4, 
3));
-        datatableEntryMap.put("TST_TST_TST_cd_itsADropdown", 
createdCodeValueId);
+        datatableEntryMap.put(tst_tst_tst + "_cd_itsADropdown", 
createdCodeValueId);
         datatableEntryMap.put("itsANumber", Utils.randomNumberGenerator(5));
         datatableEntryMap.put("itsAString", Utils.randomStringGenerator("", 
8));
         datatableEntryMap.put("itsAText", Utils.randomStringGenerator("", 
1000));
@@ -482,8 +528,8 @@ public class DatatableIntegrationTest {
                 Utils.arrayDateTimeToString((List) ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(4)));
         assertEquals("itsADecimal", ((Map) ((List) 
items.get("columnHeaders")).get(5)).get("columnName"));
         assertEquals(datatableEntryMap.get("itsADecimal"), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(5));
-        assertEquals("TST_TST_TST_cd_itsADropdown", ((Map) ((List) 
items.get("columnHeaders")).get(6)).get("columnName"));
-        assertEquals(datatableEntryMap.get("TST_TST_TST_cd_itsADropdown"),
+        assertEquals(tst_tst_tst + "_cd_itsADropdown", ((Map) ((List) 
items.get("columnHeaders")).get(6)).get("columnName"));
+        assertEquals(datatableEntryMap.get(tst_tst_tst + "_cd_itsADropdown"),
                 ((List) ((Map) ((List) 
items.get("data")).get(0)).get("row")).get(6));
         assertEquals("itsANumber", ((Map) ((List) 
items.get("columnHeaders")).get(7)).get("columnName"));
         assertEquals(datatableEntryMap.get("itsANumber"), ((List) ((Map) 
((List) items.get("data")).get(0)).get("row")).get(7));
@@ -500,7 +546,7 @@ public class DatatableIntegrationTest {
         assertEquals(datatableEntryMap.get("itsADatetime"),
                 Utils.arrayDateTimeToString((List) ((List) ((Map) ((List) 
items.get("data")).get(1)).get("row")).get(4)));
         assertEquals(datatableEntryMap.get("itsADecimal"), ((List) ((Map) 
((List) items.get("data")).get(1)).get("row")).get(5));
-        assertEquals(datatableEntryMap.get("TST_TST_TST_cd_itsADropdown"),
+        assertEquals(datatableEntryMap.get(tst_tst_tst + "_cd_itsADropdown"),
                 ((List) ((Map) ((List) 
items.get("data")).get(1)).get("row")).get(6));
         assertEquals(datatableEntryMap.get("itsANumber"), ((List) ((Map) 
((List) items.get("data")).get(1)).get("row")).get(7));
         assertEquals(datatableEntryMap.get("itsAString"), ((List) ((Map) 
((List) items.get("data")).get(1)).get("row")).get(8));
@@ -520,8 +566,8 @@ public class DatatableIntegrationTest {
         assertEquals(datatableEntryMap.get("itsADecimal"), 
datatableEntryResponseNoGenericResult.get(0).get("itsADecimal"));
         assertEquals(datatableEntryMap.get("itsADatetime"),
                 Utils.arrayDateTimeToString((List<Integer>) 
datatableEntryResponseNoGenericResult.get(0).get("itsADatetime")));
-        assertEquals(datatableEntryMap.get("TST_TST_TST_cd_itsADropdown"),
-                
datatableEntryResponseNoGenericResult.get(0).get("TST_TST_TST_cd_itsADropdown"));
+        assertEquals(datatableEntryMap.get(tst_tst_tst + "_cd_itsADropdown"),
+                datatableEntryResponseNoGenericResult.get(0).get(tst_tst_tst + 
"_cd_itsADropdown"));
         assertEquals(datatableEntryMap.get("itsANumber"), 
datatableEntryResponseNoGenericResult.get(0).get("itsANumber"));
         assertEquals(datatableEntryMap.get("itsAString"), 
datatableEntryResponseNoGenericResult.get(0).get("itsAString"));
         assertEquals(datatableEntryMap.get("itsAText"), 
datatableEntryResponseNoGenericResult.get(0).get("itsAText"));
@@ -534,7 +580,7 @@ public class DatatableIntegrationTest {
         datatableEntryMap.put("itsADate", null);
         datatableEntryMap.put("itsADatetime", null);
         datatableEntryMap.put("itsADecimal", null);
-        datatableEntryMap.put("TST_TST_TST_cd_itsADropdown", null);
+        datatableEntryMap.put(tst_tst_tst + "_cd_itsADropdown", null);
         datatableEntryMap.put("itsANumber", null);
         datatableEntryMap.put("itsAString", null);
         datatableEntryMap.put("itsAText", null);
@@ -555,7 +601,7 @@ public class DatatableIntegrationTest {
         assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsADate"));
         assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsADecimal"));
         assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsADatetime"));
-        assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get("TST_TST_TST_cd_itsADropdown"));
+        assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get(tst_tst_tst + 
"_cd_itsADropdown"));
         assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsANumber"));
         assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsAString"));
         assertEquals(null, ((Map) 
updatedDatatableEntryResponse.get("changes")).get("itsAText"));
@@ -574,7 +620,7 @@ public class DatatableIntegrationTest {
         assertEquals(null, ((List) ((Map) ((List) 
items.get("data")).get(1)).get("row")).get(4));
         assertEquals("itsADecimal", ((Map) ((List) 
items.get("columnHeaders")).get(5)).get("columnName"));
         assertEquals(null, ((List) ((Map) ((List) 
items.get("data")).get(1)).get("row")).get(5));
-        assertEquals("TST_TST_TST_cd_itsADropdown", ((Map) ((List) 
items.get("columnHeaders")).get(6)).get("columnName"));
+        assertEquals(tst_tst_tst + "_cd_itsADropdown", ((Map) ((List) 
items.get("columnHeaders")).get(6)).get("columnName"));
         assertEquals(null, ((List) ((Map) ((List) 
items.get("data")).get(1)).get("row")).get(6));
         assertEquals("itsANumber", ((Map) ((List) 
items.get("columnHeaders")).get(7)).get("columnName"));
         assertEquals(null, ((List) ((Map) ((List) 
items.get("data")).get(1)).get("row")).get(7));
@@ -594,8 +640,8 @@ public class DatatableIntegrationTest {
         assertEquals(datatableEntryMap.get("itsADate"), 
datatableEntryResponseNoGenericResult.get(0).get("itsADate"));
         assertEquals(datatableEntryMap.get("itsADecimal"), 
datatableEntryResponseNoGenericResult.get(0).get("itsADecimal"));
         assertEquals(datatableEntryMap.get("itsADatetime"), 
datatableEntryResponseNoGenericResult.get(0).get("itsADatetime"));
-        assertEquals(datatableEntryMap.get("TST_TST_TST_cd_itsADropdown"),
-                
datatableEntryResponseNoGenericResult.get(0).get("TST_TST_TST_cd_itsADropdown"));
+        assertEquals(datatableEntryMap.get(tst_tst_tst + "_cd_itsADropdown"),
+                datatableEntryResponseNoGenericResult.get(0).get(tst_tst_tst + 
"_cd_itsADropdown"));
         assertEquals(datatableEntryMap.get("itsANumber"), 
datatableEntryResponseNoGenericResult.get(0).get("itsANumber"));
         assertEquals(datatableEntryMap.get("itsAString"), 
datatableEntryResponseNoGenericResult.get(0).get("itsAString"));
         assertEquals(datatableEntryMap.get("itsAText"), 
datatableEntryResponseNoGenericResult.get(0).get("itsAText"));

Reply via email to