FINERACT-1911: Assign Data Table to Transaction - general

This commit is contained in:
jmarta
2023-08-17 09:25:51 +02:00
committed by Adam Saghy
parent 2275f9c301
commit b616a96fc9
38 changed files with 2419 additions and 1360 deletions
@@ -69,16 +69,12 @@ public final class ApiParameterError {
public static ApiParameterError parameterError(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final Object... defaultUserMessageArgs) {
final ApiParameterError error = new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs,
parameterName, null);
return error;
return new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs, parameterName, null);
}
public static ApiParameterError parameterErrorWithValue(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final String value, final Object... defaultUserMessageArgs) {
final ApiParameterError error = new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs,
parameterName, value);
return error;
return new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs, parameterName, value);
}
private ApiParameterError(final String globalisationMessageCode, final String defaultUserMessage, final Object[] defaultUserMessageArgs,
@@ -25,18 +25,21 @@ import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Locale;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.jetbrains.annotations.NotNull;
public final class DateUtils {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATETIME_FORMAT = DEFAULT_DATE_FORMAT + " HH:mm:ss";
public static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern(DEFAULT_DATETIME_FORMAT)
.toFormatter();
public static final DateTimeFormatter DEFAULT_DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern(DEFAULT_DATE_FORMAT)
.toFormatter();
public static final DateTimeFormatter DEFAULT_DATE_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT);
public static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATETIME_FORMAT);
private DateUtils() {
@@ -86,4 +89,106 @@ public final class DateUtils {
public static long getDifferenceInDays(final LocalDate localDateBefore, final LocalDate localDateAfter) {
return DAYS.between(localDateBefore, localDateAfter);
}
public static int compareToBusinessDate(LocalDate date) {
return compare(date, getBusinessLocalDate());
}
public static boolean isEqualBusinessDate(LocalDate date) {
return isEqual(date, getBusinessLocalDate());
}
public static boolean isBeforeBusinessDate(LocalDate date) {
return isBefore(date, getBusinessLocalDate());
}
public static boolean isAfterBusinessDate(LocalDate date) {
return isAfter(date, getBusinessLocalDate());
}
public static int compare(LocalDate first, LocalDate second) {
return first == null ? (second == null ? 0 : -1) : first.compareTo(second);
}
public static boolean isEqual(LocalDate first, LocalDate second) {
return first == null ? second == null : (second != null && first.isEqual(second));
}
public static boolean isBefore(LocalDate first, LocalDate second) {
return second != null && (first == null || first.isBefore(second));
}
public static boolean isAfter(LocalDate first, LocalDate second) {
return first != null && (second == null || first.isAfter(second));
}
public static LocalDate parseLocalDate(String stringDate) {
return parseLocalDate(stringDate, null);
}
public static LocalDate parseLocalDate(String stringDate, String format) {
return parseLocalDate(stringDate, format, null);
}
public static LocalDate parseLocalDate(String stringDate, String format, Locale locale) {
if (stringDate == null) {
return null;
}
DateTimeFormatter formatter = getDateFormatter(format, locale);
try {
return LocalDate.parse(stringDate, formatter);
} catch (final DateTimeParseException e) {
final List<ApiParameterError> errors = List.of(ApiParameterError.parameterError("validation.msg.invalid.date.pattern",
"The parameter date (" + stringDate + ") format is invalid", "date", stringDate));
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", errors, e);
}
}
public static String format(LocalDate date) {
return format(date, null);
}
public static String format(LocalDate date, String format) {
return format(date, format, null);
}
public static String format(LocalDate date, String format, Locale locale) {
return date == null ? null : date.format(getDateFormatter(format, locale));
}
public static String format(LocalDateTime dateTime) {
return format(dateTime, null);
}
public static String format(LocalDateTime dateTime, String format) {
return format(dateTime, format, null);
}
public static String format(LocalDateTime dateTime, String format, Locale locale) {
return dateTime == null ? null : dateTime.format(getDateTimeFormatter(format, locale));
}
@NotNull
private static DateTimeFormatter getDateFormatter(String format, Locale locale) {
DateTimeFormatter formatter = DEFAULT_DATE_FORMATTER;
if (format != null || locale != null) {
if (format == null) {
format = DEFAULT_DATE_FORMAT;
}
formatter = locale == null ? DateTimeFormatter.ofPattern(format) : DateTimeFormatter.ofPattern(format, locale);
}
return formatter;
}
@NotNull
private static DateTimeFormatter getDateTimeFormatter(String format, Locale locale) {
DateTimeFormatter formatter = DEFAULT_DATETIME_FORMATTER;
if (format != null || locale != null) {
if (format == null) {
format = DEFAULT_DATETIME_FORMAT;
}
formatter = locale == null ? DateTimeFormatter.ofPattern(format) : DateTimeFormatter.ofPattern(format, locale);
}
return formatter;
}
}
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.interoperation.util;
package org.apache.fineract.infrastructure.core.service;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
@@ -31,6 +31,10 @@ public final class MathUtil {
}
public static <E extends Number> E nullToDefault(E value, E def) {
return value == null ? def : value;
}
public static Long nullToZero(Long value) {
return nullToDefault(value, 0L);
}
@@ -328,6 +332,10 @@ public final class MathUtil {
return isEmpty(amount) ? amount : amount.negate(mc);
}
public static String formatToSql(BigDecimal amount) {
return amount == null ? null : amount.toPlainString();
}
// ----------------- Money -----------------
public static Money nullToZero(Money value, @NotNull MonetaryCurrency currency) {
@@ -20,8 +20,14 @@ package org.apache.fineract.infrastructure.core.service.database;
import static java.lang.String.format;
import jakarta.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
@Component
@@ -36,14 +42,19 @@ public class DatabaseSpecificSQLGenerator {
}
public String escape(String arg) {
if (databaseTypeResolver.isMySQL()) {
DatabaseType dialect = databaseTypeResolver.databaseType();
if (dialect.isMySql()) {
return format("`%s`", arg);
} else if (databaseTypeResolver.isPostgreSQL()) {
} else if (dialect.isPostgres()) {
return format("\"%s\"", arg);
}
return arg;
}
public String formatValue(JdbcJavaType columnType, String value) {
return (columnType.isStringType() || columnType.isAnyDateType()) ? format("'%s'", value) : value;
}
public String groupConcat(String arg) {
if (databaseTypeResolver.isMySQL()) {
return format("GROUP_CONCAT(%s)", arg);
@@ -189,4 +200,49 @@ public class DatabaseSpecificSQLGenerator {
throw new IllegalStateException("Database type is not supported for casting to json " + databaseTypeResolver.databaseType());
}
}
public String alias(@NotNull String field, String alias) {
return Strings.isEmpty(alias) ? field : (alias + '.') + field;
}
public String buildSelect(Collection<String> fields, String alias, boolean embedded) {
if (fields == null || fields.isEmpty()) {
return "";
}
String select = "";
if (!embedded) {
select = "SELECT ";
}
return select + fields.stream().map(e -> alias(escape(e), alias)).collect(Collectors.joining(", "));
}
public String buildFrom(String definition, String alias, boolean embedded) {
if (definition == null) {
return "";
}
String from = "";
if (!embedded) {
from = "FROM ";
}
return from + escape(definition) + (Strings.isEmpty(alias) ? "" : (" " + alias));
}
public String buildJoin(@NotNull String definition, String alias, @NotNull String fkCol, String refAlias, @NotNull String refCol,
String joinType) {
String join = Strings.isEmpty(joinType) ? "JOIN" : (joinType + " JOIN");
alias = Strings.isEmpty(alias) ? "" : (" " + alias);
return format("%s %s%s ON %s = %s", join, escape(definition), alias, alias(escape(fkCol), alias), alias(escape(refCol), refAlias));
}
public String buildOrderBy(List<Sort.Order> orders, String alias, boolean embedded) {
if (orders == null || orders.isEmpty()) {
return "";
}
String orderBy = "";
if (!embedded) {
orderBy = "ORDER BY ";
}
return orderBy + orders.stream().map(e -> String.join(" ", alias(escape(e.getProperty()), alias), e.getDirection().name()))
.collect(Collectors.joining(", "));
}
}
@@ -19,5 +19,16 @@
package org.apache.fineract.infrastructure.core.service.database;
public enum DatabaseType {
MYSQL, POSTGRESQL
MYSQL, //
POSTGRESQL, //
;
public boolean isMySql() {
return this == MYSQL;
}
public boolean isPostgres() {
return this == POSTGRESQL;
}
}
@@ -58,10 +58,10 @@ public class DatabaseTypeResolver implements InitializingBean {
}
public boolean isPostgreSQL() {
return DatabaseType.POSTGRESQL.equals(currentDatabaseType.get());
return DatabaseType.POSTGRESQL == currentDatabaseType.get();
}
public boolean isMySQL() {
return DatabaseType.MYSQL.equals(currentDatabaseType.get());
return DatabaseType.MYSQL == currentDatabaseType.get();
}
}
@@ -0,0 +1,371 @@
/**
* 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.core.service.database;
import jakarta.validation.constraints.NotNull;
import java.io.InputStream;
import java.io.Reader;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.springframework.data.domain.Persistable;
public enum JavaType {
BOOLEAN(boolean.class), //
BYTE(byte.class), //
SHORT(short.class), //
INT(int.class), //
LONG(long.class), //
CHAR(char.class), //
FLOAT(float.class), //
DOUBLE(double.class), //
// non-primitive types
BOOLEAN_OBJ(Boolean.class), //
BYTE_OBJ(Byte.class), //
SHORT_OBJ(Short.class), //
INT_OBJ(Integer.class), //
LONG_OBJ(Long.class), //
CHAR_OBJ(Character.class), //
FLOAT_OBJ(Float.class), //
DOUBLE_OBJ(Double.class), //
BIGDECIMAL(BigDecimal.class), //
BIGINTEGER(BigInteger.class), //
NUMBER(Number.class), //
STRING(String.class), //
DATETIME(Timestamp.class), //
TIME(Time.class), //
DATE(Date.class), //
// Java8 time API
LOCAL_DATE(LocalDate.class), //
LOCAL_TIME(LocalTime.class), //
LOCAL_DATETIME(LocalDateTime.class), //
OFFSET_TIME(OffsetTime.class), //
OFFSET_DATETIME(OffsetDateTime.class), //
BINARY(byte[].class), //
ARRAY(null), //
COLLECTION(Collection.class), //
MAP(Map.class), //
PC(null), //
LOCALE(Locale.class), //
PC_UNTYPED(Persistable.class), //
CALENDAR(Calendar.class), //
OID(null), //
INPUT_STREAM(InputStream.class), //
INPUT_READER(Reader.class), //
ENUM(Enum.class), //
OBJECT(Object.class), //
;
public static final JavaType[] VALUES = values();
private static final Map<Class<?>, JavaType> BY_TYPE = Arrays.stream(VALUES).filter(e -> e.type != null)
.collect(Collectors.toMap(JavaType::getTypeClass, v -> v));
private final Class<?> type;
JavaType(Class<?> type) {
this.type = type;
}
public Class<?> getTypeClass() {
return type;
}
public boolean isPrimitive() {
return ordinal() <= DOUBLE.ordinal();
}
public boolean isBooleanType() {
return this == BOOLEAN || this == BOOLEAN_OBJ;
}
public boolean isByteType() {
return this == BYTE || this == BYTE_OBJ;
}
public boolean isShortType() {
return this == SHORT || this == SHORT_OBJ;
}
public boolean isIntegerType() {
return this == INT || this == INT_OBJ;
}
public boolean isLongType() {
return this == LONG || this == LONG_OBJ;
}
public boolean isAnyIntegerType() {
return isByteType() || isShortType() || isIntegerType() || isLongType() || this == BIGINTEGER;
}
public boolean isFloatType() {
return this == FLOAT || this == FLOAT_OBJ;
}
public boolean isDoubleType() {
return this == DOUBLE || this == DOUBLE_OBJ;
}
public boolean isAnyFloatType() {
return isFloatType() || isDoubleType();
}
public boolean isDecimalType() {
return this == BIGDECIMAL;
}
public boolean isObjectNumericType() {
return Number.class.isAssignableFrom(getTypeClass());
}
public boolean isPrimitiveNumericType() {
return isPrimitive() && this != BOOLEAN && this != CHAR;
}
public boolean isNumericType() {
return isPrimitiveNumericType() || isObjectNumericType();
}
public boolean isStringType() {
return this == STRING;
}
public boolean isCharacterType() {
return this == CHAR || this == CHAR_OBJ;
}
public boolean isDateType() {
return this == DATE || this == LOCAL_DATE;
}
public boolean isTimeType() {
return this == TIME || this == LOCAL_TIME || this == OFFSET_TIME;
}
public boolean isDateTimeType() {
return this == DATETIME || this == LOCAL_DATETIME || this == OFFSET_DATETIME;
}
public boolean isAnyDateType() {
return isDateType() || isTimeType() || isDateTimeType();
}
public boolean isBinaryType() {
return this == BINARY;
}
public JavaType getPrimitiveType() {
if (isPrimitive()) {
return this;
}
return switch (this) {
case BOOLEAN_OBJ -> BOOLEAN;
case BYTE_OBJ -> BYTE;
case SHORT_OBJ -> SHORT;
case INT_OBJ -> INT;
case LONG_OBJ -> LONG;
case CHAR_OBJ -> CHAR;
case DOUBLE_OBJ -> DOUBLE;
case FLOAT_OBJ -> FLOAT;
default -> null;
};
}
@NotNull
public JavaType getObjectType() {
if (!isPrimitive()) {
return this;
}
return switch (this) {
case BOOLEAN -> BOOLEAN_OBJ;
case BYTE -> BYTE_OBJ;
case SHORT -> SHORT_OBJ;
case INT -> INT_OBJ;
case LONG -> LONG_OBJ;
case CHAR -> CHAR_OBJ;
case DOUBLE -> DOUBLE_OBJ;
case FLOAT -> FLOAT_OBJ;
default ->
throw new PlatformServiceUnavailableException("error.msg.database.type.not.allowed", "Unknown primitive type " + this);
};
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
@NotNull
public static JavaType forType(Class<?> type) {
if (type == null) {
return OBJECT;
}
JavaType javaType = BY_TYPE.get(type);
if (javaType != null) {
return javaType;
}
if (type.isArray()) {
return ARRAY;
}
// have to do this first to catch custom collection and map types;
// on resolve we figure out if these custom types are
// persistence-capable
for (Class<?> aType : BY_TYPE.keySet()) {
if (aType.isAssignableFrom(type)) {
return BY_TYPE.get(aType);
}
}
if (type.isInterface()) {
return Serializable.class.isAssignableFrom(type) ? OBJECT : PC_UNTYPED;
}
if (type.isAssignableFrom(Reader.class)) {
return INPUT_READER;
}
if (type.isAssignableFrom(InputStream.class)) {
return INPUT_STREAM;
}
return OBJECT;
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
public boolean matchType(Class<?> type) {
return matchType(type, true, false);
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
public boolean matchType(Class<?> type, boolean strict) {
return matchType(type, strict, false);
}
private boolean matchType(Class<?> type, boolean strict, boolean embedded) {
if (type == null) {
return this == OBJECT;
}
if (this.type == null) {
return false;
}
if (type.equals(this.type)) {
return true;
}
if (type.isArray()) {
return this.type.isArray();
}
if (this.type.isAssignableFrom(type)) {
return true;
}
if (type.isInterface()) {
return Serializable.class.isAssignableFrom(type) ? this == OBJECT : this == PC_UNTYPED;
}
if (type.isAssignableFrom(Reader.class)) {
return this == INPUT_READER;
}
if (type.isAssignableFrom(InputStream.class)) {
return this == INPUT_STREAM;
}
if (!strict && !embedded) {
JavaType thisType = this;
if (isPrimitive() && !type.isPrimitive()) {
thisType = getObjectType();
} else if (type.isPrimitive()) {
JavaType primitiveType = getPrimitiveType();
if (primitiveType != null) {
thisType = primitiveType;
}
}
if (this != thisType && thisType.matchType(type, false, true)) {
return true;
}
if (Number.class.isAssignableFrom(type) && isNumericType()) {
JavaType thatType = forType(type);
if (thisType.isAnyIntegerType() && thatType.isAnyIntegerType()) {
return thatType.ordinal() <= thisType.ordinal();
}
if (thisType.isAnyFloatType() && thatType.isAnyFloatType()) {
return thatType.ordinal() <= thisType.ordinal();
}
}
}
return this == OBJECT;
}
/**
* Determine whether the provided Object value is the default for this java type. For example: If o = Integer(0) and
* typeCode = JavaTypes.INT, this method will return true.
*/
public boolean isDefault(Object o) {
return switch (this) {
case BOOLEAN, BOOLEAN_OBJ -> Boolean.FALSE.equals(o);
case BYTE, BYTE_OBJ -> o != null && ((Byte) o) == 0;
case SHORT, SHORT_OBJ -> o != null && ((Short) o) == 0;
case INT, INT_OBJ -> o != null && ((Integer) o) == 0;
case LONG, LONG_OBJ -> o != null && ((Long) o) == 0L;
case CHAR, CHAR_OBJ -> o != null && ((Character) o) == '\u0000';
case FLOAT, FLOAT_OBJ -> o != null && ((Float) o) == 0.0F;
case DOUBLE, DOUBLE_OBJ -> o != null && ((Double) o) == 0.0d;
default -> false;
};
}
public Object parse(String s, String format, Locale locale) {
if (s == null) {
return null;
}
return switch (this) {
case BOOLEAN, BOOLEAN_OBJ -> Boolean.valueOf(s);
case BYTE, BYTE_OBJ -> Byte.parseByte(s);
case SHORT, SHORT_OBJ -> Short.parseShort(s);
case INT, INT_OBJ -> Integer.parseInt(s);
case LONG, LONG_OBJ -> Long.parseLong(s);
case CHAR, CHAR_OBJ -> StringUtils.isEmpty(s) ? null : s.charAt(0);
case FLOAT, FLOAT_OBJ -> Float.parseFloat(s);
case DOUBLE, DOUBLE_OBJ -> Double.parseDouble(s);
case BIGDECIMAL -> new BigDecimal(s, MoneyHelper.getMathContext());
case BIGINTEGER -> new BigInteger(s);
case STRING -> s;
case DATETIME -> Timestamp.valueOf(s);
case TIME -> Time.valueOf(s);
case DATE -> new Date(s);
default -> throw new PlatformServiceUnavailableException("error.msg.database.type.parse",
"Parse string representation is not supported " + this);
};
}
}
@@ -0,0 +1,342 @@
/**
* 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.core.service.database;
import com.google.common.collect.ImmutableList;
import jakarta.validation.constraints.NotNull;
import java.sql.JDBCType;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
public enum JdbcJavaType {
// Mysql, PostgreSql
BIT(JavaType.BOOLEAN, new DialectType(JDBCType.BIT), new DialectType(JDBCType.BIT, true)) { //
@Override
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return Boolean.TRUE.equals(value) ? 1 : 0;
}
},
BOOLEAN(JavaType.BOOLEAN, new DialectType(JDBCType.BIT), new DialectType(JDBCType.BOOLEAN, null, "BOOL")) { //
@Override
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return dialect.isMySql() ? (Boolean.TRUE.equals(value) ? 1 : 0) : super.toJdbcValueImpl(dialect, value);
}
},
SMALLINT(JavaType.SHORT, new DialectType(JDBCType.SMALLINT, true), new DialectType(JDBCType.SMALLINT, null, "INT2")), //
TINYINT(JavaType.SHORT, new DialectType(JDBCType.TINYINT, true), new DialectType(JDBCType.SMALLINT)) { //
@Override
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return dialect.isMySql() && value instanceof Boolean ? (Boolean.TRUE.equals(value) ? 1 : 0)
: super.toJdbcValueImpl(dialect, value);
}
},
INTEGER(JavaType.INT, new DialectType(JDBCType.INTEGER, null, true, "INT"), new DialectType(JDBCType.INTEGER, null, "INT", "INT4")), //
MEDIUMINT(JavaType.INT, new DialectType(JDBCType.INTEGER, "MEDIUMINT", true), new DialectType(JDBCType.INTEGER)), //
BIGINT(JavaType.LONG, new DialectType(JDBCType.BIGINT, true), new DialectType(JDBCType.BIGINT, null, "INT8")), //
REAL(JavaType.FLOAT, new DialectType(JDBCType.FLOAT, true, true), new DialectType(JDBCType.REAL)), //
FLOAT(JavaType.FLOAT, new DialectType(JDBCType.FLOAT, true, true), new DialectType(JDBCType.REAL, null, "FLOAT4")), //
DOUBLE(JavaType.DOUBLE, new DialectType(JDBCType.DOUBLE, null, true, true, "DOUBLE PRECISION", "REAL"),
new DialectType(JDBCType.DOUBLE, "DOUBLE PRECISION", "FLOAT8")), //
NUMERIC(JavaType.BIGDECIMAL, new DialectType(JDBCType.NUMERIC, true, true), new DialectType(JDBCType.NUMERIC, true, true)), //
DECIMAL(JavaType.BIGDECIMAL, new DialectType(JDBCType.DECIMAL, "DECIMAL", true, true, "DEC", "FIXED"),
new DialectType(JDBCType.DECIMAL, true, true)), //
SERIAL(JavaType.INT, new DialectType(JDBCType.INTEGER, "{type} AUTO_INCREMENT", true, false),
new DialectType(JDBCType.INTEGER, "SERIAL", "SERIAL4")), //
SMALLSERIAL(JavaType.SHORT, new DialectType(JDBCType.SMALLINT, "{type} AUTO_INCREMENT", true, false),
new DialectType(JDBCType.SMALLINT, "SMALLSERIAL")), //
BIGSERIAL(JavaType.LONG, new DialectType(JDBCType.BIGINT, "{type} AUTO_INCREMENT", true),
new DialectType(JDBCType.BIGINT, "BIGSERIAL", "SERIAL8")), //
MONEY(JavaType.BIGDECIMAL, new DialectType(JDBCType.DECIMAL, "DECIMAL(19,2)"), new DialectType(JDBCType.DECIMAL, "MONEY")), //
CHAR(JavaType.STRING, new DialectType(JDBCType.CHAR, true), new DialectType(JDBCType.CHAR, null, true, "CHARACTER", "BPCHAR")), //
VARCHAR(JavaType.STRING, new DialectType(JDBCType.VARCHAR, true), new DialectType(JDBCType.VARCHAR, null, true, "CHARACTER VARYING")), //
LONGVARCHAR(JavaType.STRING, new DialectType(JDBCType.VARCHAR, true), new DialectType(JDBCType.VARCHAR, true)), //
TEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "TEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
TINYTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "TINYTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
MEDIUMTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "MEDIUMTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
LONGTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "LONGTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
DATE(JavaType.LOCAL_DATE, new DialectType(JDBCType.DATE), new DialectType(JDBCType.DATE)), //
// precision for TIME, TIMESTAMP (postgres) and INTERVAL specifies the number of fractional digits retained in the
// seconds field, but by default, there is no explicit bound on precision
TIME(JavaType.LOCAL_TIME, new DialectType(JDBCType.TIME), new DialectType(JDBCType.TIME, null, "TIME WITHOUT TIME ZONE")), //
TIME_WITH_TIMEZONE(JavaType.OFFSET_TIME, new DialectType(JDBCType.TIME_WITH_TIMEZONE, "TIME"),
new DialectType(JDBCType.TIME_WITH_TIMEZONE, null, "TIME WITH TIME ZONE")), //
DATETIME(JavaType.LOCAL_DATETIME, new DialectType(JDBCType.TIMESTAMP, "DATETIME", "TIMESTAMP"),
new DialectType(JDBCType.TIMESTAMP, null, "TIMESTAMP WITHOUT TIME ZONE")), //
TIMESTAMP_WITH_TIMEZONE(JavaType.OFFSET_DATETIME, new DialectType(JDBCType.TIMESTAMP_WITH_TIMEZONE, null, "DATETIME"),
new DialectType(JDBCType.TIMESTAMP_WITH_TIMEZONE, null, "TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ")), //
INTERVAL(JavaType.TIME, new DialectType(JDBCType.TIME), new DialectType(JDBCType.TIME, "INTERVAL")), //
BINARY(JavaType.BINARY, new DialectType(JDBCType.BINARY, true), new DialectType(JDBCType.BINARY, "BYTEA")), //
VARBINARY(JavaType.BINARY, new DialectType(JDBCType.VARBINARY, true), new DialectType(JDBCType.VARBINARY, "BYTEA")), //
LONGVARBINARY(JavaType.BINARY, new DialectType(JDBCType.VARBINARY, true), new DialectType(JDBCType.VARBINARY, "BYTEA")), //
BYTEA(JavaType.BINARY, new DialectType(JDBCType.BLOB, "LONGBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
BLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "BLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
TINYBLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "TINYBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
MEDIUMBLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "MEDIUMBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
LONGBLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "LONGBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
;
// /* Not used types */
// /* *** java.sql.JDBCType *** */
// NULL, OTHER, JAVA_OBJECT, DISTINCT, STRUCT, ARRAY, CLOB, REF, DATALINK
// /* JDBC 4.0 Types */
// ROWID, NCHAR(Types.NCHAR), NVARCHAR, NCLOB, SQLXML
// /* JDBC 4.2 Types */
// REF_CURSOR, TIME_WITH_TIMEZONE, TIMESTAMP_WITH_TIMEZONE
// /* *** POSTGRES *** */
// bit varying [(n)] / varbit, box, cidr, circle, inet, interval, line, lseg, macaddr, path, point, polygon, time
// [(p)] with time zone, timez,
// timestamp [(p)] with time zone, timestamptz, bit(n), bit varying(n), array[n]
// /* *** MYSQL *** */
// year, enum, set, spatial types, JSON
private static final String PLACEHOLDER_TYPE = "{type}";
@NotNull
private final JavaType javaType;
private final ImmutableList<DialectType> dialectTypes;
JdbcJavaType(@NotNull JavaType javaType, DialectType... dialectTypes) {
this.javaType = javaType;
this.dialectTypes = ImmutableList.copyOf(dialectTypes);
}
@NotNull
public JavaType getJavaType() {
return javaType;
}
public static JdbcJavaType getByTypeName(@NotNull DatabaseType dialect, String name) {
if (name == null) {
return null;
}
name = name.toUpperCase();
for (JdbcJavaType type : values()) {
DialectType dialectType = type.getDialectType(dialect);
if (dialectType.getNameResolved().equals(name)) {
return type;
}
if (dialectType.alterNames != null) {
for (String alterName : dialectType.alterNames) {
if (alterName.equals(name)) {
return type;
}
}
}
}
return null;
}
@NotNull
private DialectType getDialectType(@NotNull DatabaseType dialect) {
DialectType dialectType = dialectTypes.get(dialect.ordinal());
if (dialectType == null) {
throw new PlatformServiceUnavailableException("error.msg.database.dialect.not.allowed",
"Dialect " + dialect + " is not supported for " + this);
}
return dialectType;
}
public boolean isBooleanType() {
return getJavaType().isBooleanType();
}
public boolean canBooleanType(@NotNull DatabaseType dialect) {
return isBooleanType() || (dialect.isMySql() && this == TINYINT);
}
public boolean isStringType() {
// ("char", "varchar", "blob", "text", "tinyblob", "tinytext", "mediumblob", "mediumtext", "longblob",
// "longtext")
return getJavaType().isStringType();
}
public boolean isVarcharType() {
// ("char", "varchar", "blob", "text", "tinyblob", "tinytext", "mediumblob", "mediumtext", "longblob",
// "longtext")
return this == VARCHAR || this == LONGVARCHAR;
}
public boolean isTextType() {
return this == TEXT || this == TINYTEXT || this == MEDIUMTEXT || this == LONGTEXT;
}
public boolean isSerialType() {
return this == SERIAL || this == SMALLSERIAL || this == BIGSERIAL;
}
public boolean isIntegerType() {
return getJavaType().isIntegerType();
}
public boolean isAnyIntegerType() {
return getJavaType().isAnyIntegerType();
}
public boolean isFloatType() {
return getJavaType().isFloatType();
}
public boolean isAnyFloatType() {
return getJavaType().isAnyFloatType();
}
public boolean isDecimalType() {
return getJavaType().isDecimalType();
}
public boolean isNumericType() {
return getJavaType().isNumericType();
}
public boolean isDateType() {
return getJavaType().isDateType();
}
public boolean isTimeType() {
return getJavaType().isTimeType();
}
public boolean isDateTimeType() {
return getJavaType().isDateTimeType();
}
public boolean isAnyDateType() {
return getJavaType().isAnyDateType();
}
public boolean isBlobType() {
return this == TINYBLOB || this == MEDIUMBLOB || this == BLOB || this == LONGBLOB;
}
public boolean isBinaryType() {
return getJavaType().isBinaryType();
}
public boolean hasPrecision(@NotNull DatabaseType dialect) {
return getDialectType(dialect).precision;
}
public boolean hasScale(@NotNull DatabaseType dialect) {
return getDialectType(dialect).scale;
}
public String getJdbcName(@NotNull DatabaseType dialect) {
DialectType dialectType = getDialectType(dialect);
return dialectType.getNameResolved();
}
public String formatSql(@NotNull DatabaseType dialect, Integer precision) {
return formatSql(dialect, precision, null);
}
public String formatSql(@NotNull DatabaseType dialect) {
return formatSql(dialect, null, null);
}
public String formatSql(@NotNull DatabaseType dialect, Integer precision, Integer scale) {
DialectType dialectType = getDialectType(dialect);
return dialectType.formatSql(precision, scale);
}
public Object toJdbcValue(@NotNull DatabaseType dialect, Object value, boolean check) {
if (value != null && check && !javaType.getObjectType().matchType(value.getClass(), false)) {
throw new PlatformServiceUnavailableException("error.msg.database.type.not.allowed",
"Data type of parameter " + value + " does not match " + this);
}
return toJdbcValueImpl(dialect, value);
}
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return value;
}
@com.google.errorprone.annotations.Immutable
private static final class DialectType {
@NotNull
private final JDBCType jdbcType;
@NotNull
private final String name;
private final boolean precision;
private final boolean scale;
private final ImmutableList<String> alterNames;
private DialectType(@NotNull JDBCType jdbcType, String name, boolean precision, boolean scale, String... alterNames) {
this.jdbcType = jdbcType;
this.name = name == null ? jdbcType.getName() : name;
this.precision = precision;
this.scale = scale;
this.alterNames = ImmutableList.copyOf(alterNames);
}
private DialectType(@NotNull JDBCType jdbcType, String name, boolean precision, String... alterNames) {
this(jdbcType, name, precision, false, alterNames);
}
private DialectType(@NotNull JDBCType jdbcType, String name, boolean precision) {
this(jdbcType, name, precision, false);
}
private DialectType(@NotNull JDBCType jdbcType, String name, String... alterNames) {
this(jdbcType, name, false, false, alterNames);
}
private DialectType(@NotNull JDBCType jdbcType, boolean precision, boolean scale) {
this(jdbcType, null, precision, scale);
}
private DialectType(@NotNull JDBCType jdbcType, boolean precision) {
this(jdbcType, null, precision, false);
}
private DialectType(@NotNull JDBCType jdbcType) {
this(jdbcType, null, false, false);
}
private String getNameResolved() {
return name.replace(PLACEHOLDER_TYPE, jdbcType.getName());
}
private String formatSql(Integer precision, Integer scale) {
int idx = name.indexOf(PLACEHOLDER_TYPE);
if (idx >= 0) {
return name.substring(0, idx) + addSqlPrecisionScale(jdbcType.getName(), precision, scale)
+ name.substring(idx + PLACEHOLDER_TYPE.length());
} else {
return addSqlPrecisionScale(name, precision, scale);
}
}
private String addSqlPrecisionScale(@NotNull String name, Integer precision, Integer scale) {
if (!this.precision || precision == null) {
return name;
}
String sql = name + '(' + precision;
if (this.scale || scale != null) {
sql = sql + ',' + scale;
}
sql += ')';
return sql;
}
}
}
@@ -0,0 +1,202 @@
/**
* 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.core.service.database;
import static java.lang.String.format;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
@AllArgsConstructor
@Getter
public enum SqlOperator {
EQ("="), //
NEQ("<>"), //
GTE(">="), //
LTE("<="), //
GT(">"), //
LT("<"), //
LIKE("LIKE") { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, "%" + values[0] + "%"));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s CONCAT('%%', %s, '%%')", definition, getSymbol(), placeholder);
}
},
NLIKE("NOT LIKE") { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, "%" + values[0] + "%"));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s CONCAT('%%', %s, '%%')", definition, getSymbol(), placeholder);
}
},
BTW("BETWEEN", 2) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s AND %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, values[0]),
sqlGenerator.formatValue(columnType, values[1]));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s %s AND %s", definition, getSymbol(), placeholder, placeholder);
}
},
NBTW("NOT BETWEEN", 2) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s AND %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, values[0]),
sqlGenerator.formatValue(columnType, values[1]));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s %s AND %s", definition, getSymbol(), placeholder, placeholder);
}
},
IN("IN", -1) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s (%s)", definition, getSymbol(),
Arrays.stream(values).map(e -> sqlGenerator.formatValue(columnType, e)).collect(Collectors.joining(", ")));
}
@Override
public boolean isPlaceholderSupported() {
return false;
}
},
NIN("NOT IN", -1) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s (%s)", definition, getSymbol(),
Arrays.stream(values).map(e -> sqlGenerator.formatValue(columnType, e)).collect(Collectors.joining(", ")));
}
@Override
public boolean isPlaceholderSupported() {
return false;
}
},
NULL("IS NULL", 0), //
NNULL("IS NOT NULL", 0), //
;
private final String symbol;
private final int paramCount;
SqlOperator(String symbol) {
this(symbol, 1);
}
public boolean isDefault() {
return this == getDefault();
}
public static SqlOperator getDefault() {
return EQ;
}
public boolean isListType() {
return paramCount < 0;
}
public String formatSql(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition, String alias,
List<String> values) {
return formatSql(sqlGenerator, columnType, definition, alias, values == null ? null : values.toArray(String[]::new));
}
public String formatSql(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition, String alias,
String... values) {
validateValues(values);
return formatImpl(sqlGenerator, columnType, sqlGenerator.alias(sqlGenerator.escape(definition), alias), values);
}
protected String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return paramCount == 0 ? format("%s %s", definition, symbol)
: format("%s %s %s", definition, symbol, sqlGenerator.formatValue(columnType, values[0]));
}
public boolean isPlaceholderSupported() {
return true;
}
public String formatPlaceholder(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, String definition, String alias) {
return formatPlaceholder(sqlGenerator, definition, alias, "?");
}
public String formatPlaceholder(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, String definition, String alias,
String placeholder) {
return formatPlaceholderImpl(sqlGenerator.alias(sqlGenerator.escape(definition), alias), placeholder);
}
protected String formatPlaceholderImpl(String definition, String placeholder) {
if (!isPlaceholderSupported()) {
throw new UnsupportedOperationException("Placeholder is not supported for this operator");
}
return paramCount == 0 ? format("%s %s", definition, symbol) : format("%s %s %s", definition, symbol, placeholder);
}
public void validateValues(String... values) {
if (values == null ? paramCount != 0 : (paramCount < 0 ? values.length < -paramCount : values.length != paramCount)) {
throw new PlatformServiceUnavailableException("error.msg.database.operator.invalid",
"Number of parameters " + Arrays.toString(values) + " must be " + Math.abs(paramCount) + " on " + this);
}
}
public void validateValues(List<?> values) {
int size = values == null ? 0 : values.size();
if (paramCount < 0 ? size < -paramCount : size != paramCount) {
throw new PlatformServiceUnavailableException("error.msg.database.operator.invalid",
"Number of parameters " + size + " must be " + Math.abs(paramCount) + " on " + this);
}
}
@NotNull
public static SqlOperator forName(String name) {
return name == null ? getDefault() : SqlOperator.valueOf(name.toUpperCase());
}
}
@@ -18,10 +18,13 @@
*/
package org.apache.fineract.infrastructure.dataqueries.data;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
/**
* Immutable data object representing a resultset column.
@@ -29,9 +32,9 @@ import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityEx
public final class ResultsetColumnHeaderData implements Serializable {
private final String columnName;
private String columnType;
private JdbcJavaType columnType;
private final Long columnLength;
private final String columnDisplayType;
private final DisplayType columnDisplayType;
private final boolean isColumnNullable;
private final boolean isColumnPrimaryKey;
private final boolean isColumnUnique;
@@ -40,8 +43,7 @@ public final class ResultsetColumnHeaderData implements Serializable {
private final List<ResultsetColumnValueData> columnValues;
private final String columnCode;
public static ResultsetColumnHeaderData basic(final String columnName, final String columnType) {
public static ResultsetColumnHeaderData basic(final String columnName, final String columnType, DatabaseType dialect) {
final Long columnLength = null;
final boolean columnNullable = false;
final boolean columnIsPrimaryKey = false;
@@ -50,21 +52,20 @@ public final class ResultsetColumnHeaderData implements Serializable {
final boolean columnIsUnique = false;
final boolean columnIsIndexed = false;
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
columnCode, columnIsUnique, columnIsIndexed);
columnCode, columnIsUnique, columnIsIndexed, dialect);
}
public static ResultsetColumnHeaderData detailed(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed) {
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed, DatabaseType dialect) {
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
columnCode, columnIsUnique, columnIsIndexed);
columnCode, columnIsUnique, columnIsIndexed, dialect);
}
private ResultsetColumnHeaderData(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed) {
private ResultsetColumnHeaderData(final String columnName, String columnType, final Long columnLength, final boolean columnNullable,
final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues, final String columnCode,
final boolean columnIsUnique, final boolean columnIsIndexed, DatabaseType dialect) {
this.columnName = columnName;
this.columnType = columnType;
this.columnLength = columnLength;
this.isColumnNullable = columnNullable;
this.isColumnPrimaryKey = columnIsPrimaryKey;
@@ -74,179 +75,20 @@ public final class ResultsetColumnHeaderData implements Serializable {
this.isColumnIndexed = columnIsIndexed;
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
adjustColumnTypes();
this.columnType = JdbcJavaType.getByTypeName(dialect, adjustColumnType(columnType));
String displayType = null;
if (this.columnCode == null) {
if (isString()) {
displayType = "STRING";
} else if (isAnyInteger()) {
if (isInteger()) {
this.columnType = this.columnType.toUpperCase();
}
displayType = "INTEGER";
} else if (isDate()) {
displayType = "DATE";
} else if (isDateTime()) {
displayType = "DATETIME";
} else if (isDecimal()) {
displayType = "DECIMAL";
} else if (isAnyText()) {
displayType = "TEXT";
} else if (isBit() || isBoolean()) {
displayType = "BOOLEAN";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
} else {
if (isInt() || isInteger()) {
displayType = "CODELOOKUP";
} else if (isVarchar()) {
displayType = "CODEVALUE";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
}
this.columnDisplayType = displayType;
}
private void adjustColumnTypes() {
switch (this.columnType) {
case "NEWDECIMAL":
this.columnType = "DECIMAL";
break;
case "CLOB":
case "ENUM":
case "SET":
this.columnType = "varchar";
break;
case "LONGLONG":
this.columnType = "bigint";
break;
case "SHORT":
this.columnType = "smallint";
break;
case "TINY":
this.columnType = "tinyint";
break;
case "INT24":
this.columnType = "int";
break;
default:
break;
}
}
public boolean isNamed(final String columnName) {
return this.columnName.equalsIgnoreCase(columnName);
}
private boolean isAnyText() {
return isText() || isTinyText() || isMediumText() || isLongText();
}
private boolean isText() {
return "text".equalsIgnoreCase(this.columnType);
}
private boolean isTinyText() {
return "tinytext".equalsIgnoreCase(this.columnType);
}
private boolean isMediumText() {
return "mediumtext".equalsIgnoreCase(this.columnType);
}
private boolean isLongText() {
return "longtext".equalsIgnoreCase(this.columnType);
}
private boolean isDecimal() {
return "decimal".equalsIgnoreCase(this.columnType) || "NEWDECIMAL".equalsIgnoreCase(this.columnType)
|| "numeric".equalsIgnoreCase(this.columnType);
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
}
private boolean isDate() {
return "date".equalsIgnoreCase(this.columnType);
}
private boolean isDateTime() {
return "datetime".equalsIgnoreCase(this.columnType) || "timestamp".equalsIgnoreCase(this.columnType)
|| "timestamp without time zone".equalsIgnoreCase(this.columnType) || "timestamptz".equalsIgnoreCase(this.columnType)
|| "timestamp with time zone".equalsIgnoreCase(this.columnType);
}
public boolean isString() {
return isVarchar() || isChar();
}
private boolean isChar() {
return "char".equalsIgnoreCase(this.columnType) || "CHARACTER VARYING".equalsIgnoreCase(this.columnType)
|| "bpchar".equalsIgnoreCase(this.columnType);
}
private boolean isVarchar() {
return "varchar".equalsIgnoreCase(this.columnType);
}
private boolean isAnyInteger() {
return isInt() || isInteger() || isSmallInt() || isTinyInt() || isMediumInt() || isBigInt() || isLong() || isSerial();
}
private boolean isSerial() {
return "SERIAL".equalsIgnoreCase(this.columnType) || "SERIAL4".equalsIgnoreCase(this.columnType)
|| "SERIAL8".equalsIgnoreCase(this.columnType) || "SMALLSERIAL".equalsIgnoreCase(this.columnType)
|| "BIGSERIAL".equalsIgnoreCase(this.columnType);
}
private boolean isInt() {
return "int".equalsIgnoreCase(this.columnType);
}
private boolean isInteger() {
return "integer".equalsIgnoreCase(this.columnType) || "int2".equalsIgnoreCase(this.columnType)
|| "int4".equalsIgnoreCase(this.columnType);
}
private boolean isSmallInt() {
return "smallint".equalsIgnoreCase(this.columnType);
}
private boolean isTinyInt() {
return "tinyint".equalsIgnoreCase(this.columnType);
}
private boolean isMediumInt() {
return "mediumint".equalsIgnoreCase(this.columnType);
}
private boolean isBigInt() {
return "bigint".equalsIgnoreCase(this.columnType) || "int8".equalsIgnoreCase(this.columnType);
}
private boolean isLong() {
return "LONG".equalsIgnoreCase(this.columnType) || "LONGLONG".equalsIgnoreCase(this.columnType);
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
}
private boolean isBit() {
return "bit".equalsIgnoreCase(this.columnType);
}
private boolean isBoolean() {
return "boolean".equalsIgnoreCase(this.columnType) || "bool".equalsIgnoreCase(this.columnType);
this.columnDisplayType = calcDisplayType();
}
public String getColumnName() {
return this.columnName;
}
public String getColumnType() {
public boolean isNamed(final String columnName) {
return this.columnName.equalsIgnoreCase(columnName);
}
public JdbcJavaType getColumnType() {
return this.columnType;
}
@@ -270,78 +112,10 @@ public final class ResultsetColumnHeaderData implements Serializable {
return isColumnIndexed;
}
public String getColumnDisplayType() {
public DisplayType getColumnDisplayType() {
return this.columnDisplayType;
}
public boolean isDateDisplayType() {
return "DATE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isDateTimeDisplayType() {
return "DATETIME".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isIntegerDisplayType() {
return "INTEGER".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isDecimalDisplayType() {
return "DECIMAL".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isBooleanDisplayType() {
return "BOOLEAN".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeValueDisplayType() {
return "CODEVALUE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeLookupDisplayType() {
return "CODELOOKUP".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isMandatory() {
return !isOptional();
}
public boolean isOptional() {
return this.isColumnNullable;
}
public boolean hasColumnValues() {
return !this.columnValues.isEmpty();
}
public boolean isColumnValueAllowed(final String match) {
boolean allowed = false;
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.matches(match)) {
allowed = true;
}
}
return allowed;
}
public boolean isColumnValueNotAllowed(final String match) {
return !isColumnValueAllowed(match);
}
public boolean isColumnCodeNotAllowed(final Integer match) {
return !isColumnCodeAllowed(match);
}
public boolean isColumnCodeAllowed(final Integer match) {
boolean allowed = false;
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.codeMatches(match)) {
allowed = true;
}
}
return allowed;
}
public String getColumnCode() {
return this.columnCode;
}
@@ -349,4 +123,156 @@ public final class ResultsetColumnHeaderData implements Serializable {
public List<ResultsetColumnValueData> getColumnValues() {
return this.columnValues;
}
public boolean isDateDisplayType() {
return columnDisplayType == DisplayType.DATE;
}
public boolean isDateTimeDisplayType() {
return columnDisplayType == DisplayType.DATETIME;
}
public boolean isTimeDisplayType() {
return columnDisplayType == DisplayType.TIME;
}
public boolean isIntegerDisplayType() {
return columnDisplayType == DisplayType.INTEGER;
}
public boolean isDecimalDisplayType() {
return columnDisplayType == DisplayType.DECIMAL;
}
public boolean isStringDisplayType() {
return columnDisplayType == DisplayType.STRING;
}
public boolean isTextDisplayType() {
return columnDisplayType == DisplayType.TEXT;
}
public boolean isBooleanDisplayType() {
return columnDisplayType == DisplayType.BOOLEAN;
}
public boolean isCodeValueDisplayType() {
return columnDisplayType == DisplayType.CODEVALUE;
}
public boolean isCodeLookupDisplayType() {
return columnDisplayType == DisplayType.CODELOOKUP;
}
public boolean isMandatory() {
return !this.isColumnNullable;
}
public boolean hasColumnValues() {
return columnValues != null && !columnValues.isEmpty();
}
public boolean isColumnValueAllowed(final String match) {
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.matches(match)) {
return true;
}
}
return false;
}
public boolean isColumnCodeAllowed(final Integer match) {
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.codeMatches(match)) {
return true;
}
}
return false;
}
public boolean hasPrecision(@NotNull DatabaseType dialect) {
return columnType.hasPrecision(dialect);
}
// --- Calculation ---
private String adjustColumnType(String type) {
type = type.toUpperCase();
switch (type) {
case "CLOB":
case "ENUM":
case "SET":
return "VARCHAR";
case "NEWDECIMAL":
return "DECIMAL";
case "LONGLONG":
return "BIGINT";
case "SHORT":
return "SMALLINT";
case "TINY":
return "TINYINT";
case "INT24":
return "INT";
default:
return type;
}
}
@NotNull
private DisplayType calcDisplayType() {
DisplayType displayType = null;
if (this.columnCode == null) {
displayType = calcColumnDisplayType(columnType);
} else {
if (columnType.isIntegerType()) {
displayType = DisplayType.CODELOOKUP;
} else if (columnType.isVarcharType()) {
displayType = DisplayType.CODEVALUE;
}
}
if (displayType == null) {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
return displayType;
}
public static DisplayType calcColumnDisplayType(JdbcJavaType columnType) {
if (columnType.isTextType()) {
return DisplayType.TEXT;
}
if (columnType.isStringType()) {
return DisplayType.STRING;
}
if (columnType.isAnyIntegerType()) {
return DisplayType.INTEGER;
}
if (columnType.isAnyFloatType()) {
return DisplayType.FLOAT;
}
if (columnType.isDecimalType()) { // Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
return DisplayType.DECIMAL;
}
if (columnType.isDateType()) {
return DisplayType.DATE;
}
if (columnType.isDateTimeType()) {
return DisplayType.DATETIME;
}
if (columnType.isTimeType()) {
return DisplayType.TIME;
}
if (columnType.isBooleanType()) {
return DisplayType.BOOLEAN;
}
if (columnType.isBinaryType()) {
return DisplayType.BINARY;
}
return null;
}
public enum DisplayType {
TEXT, STRING, INTEGER, FLOAT, DECIMAL, DATE, TIME, DATETIME, BOOLEAN, BINARY, CODELOOKUP, CODEVALUE,;
}
}
@@ -26,9 +26,9 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.loan.LoanCOBBusinessStep;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountSnapshotBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition;
import org.apache.fineract.investor.data.ExternalTransferStatus;
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
@@ -34,9 +34,9 @@ import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountSnapshotBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.investor.data.ExternalTransferStatus;
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
@@ -71,8 +71,8 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
@@ -25,7 +25,7 @@ import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
@@ -18,13 +18,48 @@
*/
package org.apache.fineract.infrastructure.core.serialization;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.BOOLEAN;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DATE;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DATETIME;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DECIMAL;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.INTEGER;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.TEXT;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.VARCHAR;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_AFTER;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_CODE;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_INDEXED;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_LENGTH;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_MANDATORY;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_NAME;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_NEWCODE;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_NEWNAME;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_BOOLEAN;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_DATE;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_DATETIME;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_DECIMAL;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_DROPDOWN;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_NUMBER;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_STRING;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_TEXT;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_TYPE_TIMESTAMP;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_UNIQUE;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_ADDCOLUMNS;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_APPTABLE_NAME;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_CHANGECOLUMNS;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_COLUMNS;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_DATATABLE_NAME;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_DROPCOLUMNS;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_MULTIROW;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_SUBTYPE;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.TABLE_FIELD_ID;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import jakarta.validation.constraints.NotNull;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -34,6 +69,8 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -41,90 +78,33 @@ import org.springframework.stereotype.Component;
@Component
public class DatatableCommandFromApiJsonDeserializer {
public static final String DATATABLE_NAME = "datatableName";
public static final String ENTITY_SUB_TYPE = "entitySubType";
public static final String APPTABLE_NAME = "apptableName";
public static final String MULTI_ROW = "multiRow";
public static final String COLUMNS = "columns";
public static final String NAME = "name";
public static final String TYPE = "type";
public static final String LENGTH = "length";
public static final String MANDATORY = "mandatory";
public static final String CODE = "code";
public static final String CHANGE_COLUMNS = "changeColumns";
public static final String ADD_COLUMNS = "addColumns";
public static final String DROP_COLUMNS = "dropColumns";
public static final String AFTER = "after";
public static final String NEW_CODE = "newCode";
public static final String NEW_NAME = "newName";
public static final String STRING = "string";
public static final String NUMBER = "number";
public static final String BOOLEAN = "boolean";
public static final String DECIMAL = "decimal";
public static final String DATE = "date";
public static final String DATETIME = "datetime";
public static final String TEXT = "text";
public static final String DROPDOWN = "dropdown";
private static final String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
private static final String DATATABLE_COLUMN_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,}[a-zA-Z0-9]$";
private static final String INDEXED = "indexed";
private static final String UNIQUE = "unique";
public static final String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
public static final String DATATABLE_COLUMN_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,}[a-zA-Z0-9]$";
/**
* The parameters supported for this command.
*/
private static final Set<String> SUPPORTED_PARAMETERS_FOR_CREATE = new HashSet<>(
Arrays.asList(DATATABLE_NAME, ENTITY_SUB_TYPE, APPTABLE_NAME, MULTI_ROW, COLUMNS));
private static final Set<String> SUPPORTED_PARAMETERS_FOR_CREATE_COLUMNS = new HashSet<>(
Arrays.asList(NAME, TYPE, LENGTH, MANDATORY, CODE, UNIQUE, INDEXED));
private static final Set<String> SUPPORTED_PARAMETERS_FOR_UPDATE = new HashSet<>(
Arrays.asList(APPTABLE_NAME, ENTITY_SUB_TYPE, CHANGE_COLUMNS, ADD_COLUMNS, DROP_COLUMNS));
private static final Set<String> SUPPORTED_PARAMETERS_FOR_ADD_COLUMNS = new HashSet<>(
Arrays.asList(NAME, TYPE, LENGTH, MANDATORY, AFTER, CODE, UNIQUE, INDEXED));
private static final Set<String> SUPPORTED_PARAMETERS_FOR_CHANGE_COLUMNS = new HashSet<>(
Arrays.asList(NAME, NEW_NAME, LENGTH, MANDATORY, AFTER, CODE, NEW_CODE, UNIQUE, INDEXED));
private static final Set<String> SUPPORTED_PARAMETERS_FOR_DROP_COLUMNS = new HashSet<>(List.of(NAME));
private static final Object[] SUPPORTED_COLUMN_TYPES = { STRING, NUMBER, BOOLEAN, DECIMAL, DATE, DATETIME, TEXT, DROPDOWN };
private static final Set<String> SUPPORTED_PARAMETERS_FOR_CREATE = Set.of(API_PARAM_DATATABLE_NAME, API_PARAM_SUBTYPE,
API_PARAM_APPTABLE_NAME, API_PARAM_MULTIROW, API_PARAM_COLUMNS);
private static final Set<String> SUPPORTED_PARAMETERS_FOR_CREATE_COLUMNS = Set.of(API_FIELD_NAME, API_FIELD_TYPE, API_FIELD_LENGTH,
API_FIELD_MANDATORY, API_FIELD_CODE, API_FIELD_UNIQUE, API_FIELD_INDEXED);
private static final Set<String> SUPPORTED_PARAMETERS_FOR_UPDATE = Set.of(API_PARAM_APPTABLE_NAME, API_PARAM_SUBTYPE,
API_PARAM_CHANGECOLUMNS, API_PARAM_ADDCOLUMNS, API_PARAM_DROPCOLUMNS);
private static final Set<String> SUPPORTED_PARAMETERS_FOR_ADD_COLUMNS = Set.of(API_FIELD_NAME, API_FIELD_TYPE, API_FIELD_LENGTH,
API_FIELD_MANDATORY, API_FIELD_AFTER, API_FIELD_CODE, API_FIELD_UNIQUE, API_FIELD_INDEXED);
private static final Set<String> SUPPORTED_PARAMETERS_FOR_CHANGE_COLUMNS = Set.of(API_FIELD_NAME, API_FIELD_NEWNAME, API_FIELD_LENGTH,
API_FIELD_MANDATORY, API_FIELD_AFTER, API_FIELD_CODE, API_FIELD_NEWCODE, API_FIELD_UNIQUE, API_FIELD_INDEXED);
private static final Set<String> SUPPORTED_PARAMETERS_FOR_DROP_COLUMNS = Set.of(API_FIELD_NAME);
private static final Object[] SUPPORTED_COLUMN_TYPES = { API_FIELD_TYPE_STRING, API_FIELD_TYPE_NUMBER, API_FIELD_TYPE_BOOLEAN,
API_FIELD_TYPE_DECIMAL, API_FIELD_TYPE_DATE, API_FIELD_TYPE_DATETIME, API_FIELD_TYPE_TEXT, API_FIELD_TYPE_DROPDOWN };
private final FromJsonHelper fromApiJsonHelper;
private final DatabaseTypeResolver databaseTypeResolver;
@Autowired
public DatatableCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
public DatatableCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper, DatabaseTypeResolver databaseTypeResolver) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
private void validateType(final DataValidatorBuilder baseDataValidator, final JsonElement column) {
final String type = this.fromApiJsonHelper.extractStringNamed(TYPE, column);
baseDataValidator.reset().parameter(TYPE).value(type).notBlank().isOneOfTheseStringValues(SUPPORTED_COLUMN_TYPES);
if (type != null && type.equalsIgnoreCase("String")) {
if (this.fromApiJsonHelper.parameterExists(LENGTH, column)) {
final String lengthStr = this.fromApiJsonHelper.extractStringNamed(LENGTH, column);
if (lengthStr != null && !StringUtils.isWhitespace(lengthStr) && StringUtils.isNumeric(lengthStr)
&& StringUtils.isNotBlank(lengthStr)) {
final Integer length = Integer.parseInt(lengthStr);
baseDataValidator.reset().parameter(LENGTH).value(length).positiveAmount();
} else if (StringUtils.isBlank(lengthStr) || StringUtils.isWhitespace(lengthStr)) {
baseDataValidator.reset().parameter(LENGTH).failWithCode("must.be.provided.when.type.is.String");
} else if (!StringUtils.isNumeric(lengthStr)) {
baseDataValidator.reset().parameter(LENGTH).failWithCode("not.greater.than.zero");
}
} else {
baseDataValidator.reset().parameter(LENGTH).failWithCode("must.be.provided.when.type.is.String");
}
} else {
baseDataValidator.reset().parameter(LENGTH).mustBeBlankWhenParameterProvidedIs(TYPE, type);
}
final String code = this.fromApiJsonHelper.extractStringNamed(CODE, column);
if (type != null && type.equalsIgnoreCase(DROPDOWN)) {
if (code != null) {
baseDataValidator.reset().parameter(CODE).value(code).notBlank().matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
} else {
baseDataValidator.reset().parameter(CODE).value(code).cantBeBlankWhenParameterProvidedIs(TYPE, type);
}
} else {
baseDataValidator.reset().parameter(CODE).value(code).mustBeBlankWhenParameterProvided(TYPE, type);
}
this.databaseTypeResolver = databaseTypeResolver;
}
public void validateForCreate(final String json) {
@@ -132,9 +112,7 @@ public class DatatableCommandFromApiJsonDeserializer {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS_FOR_CREATE);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
@@ -142,53 +120,50 @@ public class DatatableCommandFromApiJsonDeserializer {
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String datatableName = this.fromApiJsonHelper.extractStringNamed(DATATABLE_NAME, element);
baseDataValidator.reset().parameter(DATATABLE_NAME).value(datatableName).notBlank().notExceedingLengthOf(50)
final String datatableName = this.fromApiJsonHelper.extractStringNamed(API_PARAM_DATATABLE_NAME, element);
baseDataValidator.reset().parameter(API_PARAM_DATATABLE_NAME).value(datatableName).notBlank().notExceedingLengthOf(50)
.matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
final String apptableName = this.fromApiJsonHelper.extractStringNamed(APPTABLE_NAME, element);
baseDataValidator.reset().parameter(APPTABLE_NAME).value(apptableName).notBlank().notExceedingLengthOf(50)
.isOneOfTheseStringValues(EntityTables.getEntitiesList());
final String apptableName = this.fromApiJsonHelper.extractStringNamed(API_PARAM_APPTABLE_NAME, element);
baseDataValidator.reset().parameter(API_PARAM_APPTABLE_NAME).value(apptableName).notBlank().notExceedingLengthOf(50)
.isOneOfTheseStringValues(EntityTables.getEntityNames());
validateEntitySubType(baseDataValidator, element, apptableName);
final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : "";
EntityTables entityTable = EntityTables.fromEntityName(apptableName);
validateEntitySubType(baseDataValidator, element, entityTable);
final Boolean multiRow = this.fromApiJsonHelper.extractBooleanNamed(MULTI_ROW, element);
baseDataValidator.reset().parameter(MULTI_ROW).value(multiRow).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final String fkColumnName = entityTable.getForeignKeyColumnNameOnDatatable();
final JsonArray columns = this.fromApiJsonHelper.extractJsonArrayNamed(COLUMNS, element);
baseDataValidator.reset().parameter(COLUMNS).value(columns).notNull().jsonArrayNotEmpty();
final Boolean multiRow = this.fromApiJsonHelper.extractBooleanNamed(API_PARAM_MULTIROW, element);
baseDataValidator.reset().parameter(API_PARAM_MULTIROW).value(multiRow).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final JsonArray columns = this.fromApiJsonHelper.extractJsonArrayNamed(API_PARAM_COLUMNS, element);
baseDataValidator.reset().parameter(API_PARAM_COLUMNS).value(columns).notNull().jsonArrayNotEmpty();
if (columns != null) {
for (final JsonElement column : columns) {
this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), SUPPORTED_PARAMETERS_FOR_CREATE_COLUMNS);
final String name = this.fromApiJsonHelper.extractStringNamed(NAME, column);
baseDataValidator.reset().parameter(NAME).value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
.matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String name = this.fromApiJsonHelper.extractStringNamed(API_FIELD_NAME, column);
baseDataValidator.reset().parameter(API_FIELD_NAME).value(name).notBlank()
.isNotOneOfTheseValues(TABLE_FIELD_ID, fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
validateType(baseDataValidator, column);
final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed(MANDATORY, column);
final Boolean unique = this.fromApiJsonHelper.extractBooleanNamed(UNIQUE, column);
final Boolean indexed = this.fromApiJsonHelper.extractBooleanNamed(INDEXED, column);
baseDataValidator.reset().parameter(MANDATORY).value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
baseDataValidator.reset().parameter(UNIQUE).value(unique).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
baseDataValidator.reset().parameter(INDEXED).value(indexed).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_MANDATORY, column);
final Boolean unique = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_UNIQUE, column);
final Boolean indexed = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_INDEXED, column);
baseDataValidator.reset().parameter(API_FIELD_MANDATORY).value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
baseDataValidator.reset().parameter(API_FIELD_UNIQUE).value(unique).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
baseDataValidator.reset().parameter(API_FIELD_INDEXED).value(indexed).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
}
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateEntitySubType(final DataValidatorBuilder baseDataValidator, final JsonElement element, final String apptableName) {
EntityTables entityTable = EntityTables.fromName(apptableName);
if (entityTable != null && EntityTables.CLIENT == entityTable) {
String entitySubType = this.fromApiJsonHelper.extractStringNamed(ENTITY_SUB_TYPE, element);
baseDataValidator.reset().parameter(ENTITY_SUB_TYPE).value(entitySubType).notBlank(); // Person or Entity
}
}
public void validateForUpdate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
@@ -211,108 +186,167 @@ public class DatatableCommandFromApiJsonDeserializer {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String apptableName = this.fromApiJsonHelper.extractStringNamed(APPTABLE_NAME, element);
baseDataValidator.reset().parameter(APPTABLE_NAME).value(apptableName).ignoreIfNull().notBlank()
.isOneOfTheseStringValues(EntityTables.getEntitiesList());
final String apptableName = this.fromApiJsonHelper.extractStringNamed(API_PARAM_APPTABLE_NAME, element);
baseDataValidator.reset().parameter(API_PARAM_APPTABLE_NAME).value(apptableName).ignoreIfNull().notBlank()
.isOneOfTheseStringValues(EntityTables.getEntityNames());
validateEntitySubType(baseDataValidator, element, apptableName);
EntityTables entityTable = EntityTables.fromEntityName(apptableName);
validateEntitySubType(baseDataValidator, element, entityTable);
final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : "";
final String fkColumnName = entityTable.getForeignKeyColumnNameOnDatatable();
final JsonArray changeColumns = this.fromApiJsonHelper.extractJsonArrayNamed(CHANGE_COLUMNS, element);
baseDataValidator.reset().parameter(CHANGE_COLUMNS).value(changeColumns).ignoreIfNull().jsonArrayNotEmpty();
final JsonArray changeColumns = this.fromApiJsonHelper.extractJsonArrayNamed(API_PARAM_CHANGECOLUMNS, element);
baseDataValidator.reset().parameter(API_PARAM_CHANGECOLUMNS).value(changeColumns).ignoreIfNull().jsonArrayNotEmpty();
if (changeColumns != null) {
for (final JsonElement column : changeColumns) {
this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), SUPPORTED_PARAMETERS_FOR_CHANGE_COLUMNS);
final String name = this.fromApiJsonHelper.extractStringNamed(NAME, column);
baseDataValidator.reset().parameter(NAME).value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
.matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String name = this.fromApiJsonHelper.extractStringNamed(API_FIELD_NAME, column);
baseDataValidator.reset().parameter(API_FIELD_NAME).value(name).notBlank()
.isNotOneOfTheseValues(TABLE_FIELD_ID, fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String newName = this.fromApiJsonHelper.extractStringNamed(NEW_NAME, column);
baseDataValidator.reset().parameter(NEW_NAME).value(newName).ignoreIfNull().notBlank().notExceedingLengthOf(50)
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String newName = this.fromApiJsonHelper.extractStringNamed(API_FIELD_NEWNAME, column);
baseDataValidator.reset().parameter(API_FIELD_NEWNAME).value(newName).ignoreIfNull().notBlank().notExceedingLengthOf(50)
.isNotOneOfTheseValues(TABLE_FIELD_ID, fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
if (this.fromApiJsonHelper.parameterExists(LENGTH, column)) {
final String lengthStr = this.fromApiJsonHelper.extractStringNamed(LENGTH, column);
if (this.fromApiJsonHelper.parameterExists(API_FIELD_LENGTH, column)) {
final String lengthStr = this.fromApiJsonHelper.extractStringNamed(API_FIELD_LENGTH, column);
if (StringUtils.isWhitespace(lengthStr) || !StringUtils.isNumeric(lengthStr) || StringUtils.isBlank(lengthStr)) {
baseDataValidator.reset().parameter(LENGTH).failWithCode("not.greater.than.zero");
baseDataValidator.reset().parameter(API_FIELD_LENGTH).failWithCode("not.greater.than.zero");
} else {
final Integer length = Integer.parseInt(lengthStr);
baseDataValidator.reset().parameter(LENGTH).value(length).ignoreIfNull().notBlank().positiveAmount();
baseDataValidator.reset().parameter(API_FIELD_LENGTH).value(length).ignoreIfNull().notBlank().positiveAmount();
}
}
final String code = this.fromApiJsonHelper.extractStringNamed(CODE, column);
baseDataValidator.reset().parameter(CODE).value(code).ignoreIfNull().notBlank().notExceedingLengthOf(100)
final String code = this.fromApiJsonHelper.extractStringNamed(API_FIELD_CODE, column);
baseDataValidator.reset().parameter(API_FIELD_CODE).value(code).ignoreIfNull().notBlank().notExceedingLengthOf(100)
.matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String newCode = this.fromApiJsonHelper.extractStringNamed(NEW_CODE, column);
baseDataValidator.reset().parameter(NEW_CODE).value(newCode).ignoreIfNull().notBlank().notExceedingLengthOf(100)
final String newCode = this.fromApiJsonHelper.extractStringNamed(API_FIELD_NEWCODE, column);
baseDataValidator.reset().parameter(API_FIELD_NEWCODE).value(newCode).ignoreIfNull().notBlank().notExceedingLengthOf(100)
.matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
if (StringUtils.isBlank(code) && StringUtils.isNotBlank(newCode)) {
baseDataValidator.reset().parameter(CODE).value(code).cantBeBlankWhenParameterProvidedIs(NEW_CODE, newCode);
baseDataValidator.reset().parameter(API_FIELD_CODE).value(code).cantBeBlankWhenParameterProvidedIs(API_FIELD_NEWCODE,
newCode);
}
final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed(MANDATORY, column);
baseDataValidator.reset().parameter(MANDATORY).value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_MANDATORY, column);
baseDataValidator.reset().parameter(API_FIELD_MANDATORY).value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
final Boolean after = this.fromApiJsonHelper.extractBooleanNamed(AFTER, column);
baseDataValidator.reset().parameter(AFTER).value(after).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean after = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_AFTER, column);
baseDataValidator.reset().parameter(API_FIELD_AFTER).value(after).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean unique = this.fromApiJsonHelper.extractBooleanNamed(UNIQUE, column);
baseDataValidator.reset().parameter(UNIQUE).value(unique).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean unique = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_UNIQUE, column);
baseDataValidator.reset().parameter(API_FIELD_UNIQUE).value(unique).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
final Boolean indexed = this.fromApiJsonHelper.extractBooleanNamed(INDEXED, column);
baseDataValidator.reset().parameter(INDEXED).value(indexed).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean indexed = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_INDEXED, column);
baseDataValidator.reset().parameter(API_FIELD_INDEXED).value(indexed).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
}
}
final JsonArray addColumns = this.fromApiJsonHelper.extractJsonArrayNamed(ADD_COLUMNS, element);
baseDataValidator.reset().parameter(ADD_COLUMNS).value(addColumns).ignoreIfNull().jsonArrayNotEmpty();
final JsonArray addColumns = this.fromApiJsonHelper.extractJsonArrayNamed(API_PARAM_ADDCOLUMNS, element);
baseDataValidator.reset().parameter(API_PARAM_ADDCOLUMNS).value(addColumns).ignoreIfNull().jsonArrayNotEmpty();
if (addColumns != null) {
for (final JsonElement column : addColumns) {
this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), SUPPORTED_PARAMETERS_FOR_ADD_COLUMNS);
final String name = this.fromApiJsonHelper.extractStringNamed(NAME, column);
baseDataValidator.reset().parameter(NAME).value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
.matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String name = this.fromApiJsonHelper.extractStringNamed(API_FIELD_NAME, column);
baseDataValidator.reset().parameter(API_FIELD_NAME).value(name).notBlank()
.isNotOneOfTheseValues(TABLE_FIELD_ID, fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
validateType(baseDataValidator, column);
final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed(MANDATORY, column);
baseDataValidator.reset().parameter(MANDATORY).value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean mandatory = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_MANDATORY, column);
baseDataValidator.reset().parameter(API_FIELD_MANDATORY).value(mandatory).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
final Boolean after = this.fromApiJsonHelper.extractBooleanNamed(AFTER, column);
baseDataValidator.reset().parameter(AFTER).value(after).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean after = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_AFTER, column);
baseDataValidator.reset().parameter(API_FIELD_AFTER).value(after).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean unique = this.fromApiJsonHelper.extractBooleanNamed(UNIQUE, column);
baseDataValidator.reset().parameter(UNIQUE).value(unique).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean unique = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_UNIQUE, column);
baseDataValidator.reset().parameter(API_FIELD_UNIQUE).value(unique).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
final Boolean indexed = this.fromApiJsonHelper.extractBooleanNamed(INDEXED, column);
baseDataValidator.reset().parameter(INDEXED).value(indexed).ignoreIfNull().notBlank().isOneOfTheseValues(true, false);
final Boolean indexed = this.fromApiJsonHelper.extractBooleanNamed(API_FIELD_INDEXED, column);
baseDataValidator.reset().parameter(API_FIELD_INDEXED).value(indexed).ignoreIfNull().notBlank().isOneOfTheseValues(true,
false);
}
}
final JsonArray dropColumns = this.fromApiJsonHelper.extractJsonArrayNamed(DROP_COLUMNS, element);
baseDataValidator.reset().parameter(DROP_COLUMNS).value(dropColumns).ignoreIfNull().jsonArrayNotEmpty();
final JsonArray dropColumns = this.fromApiJsonHelper.extractJsonArrayNamed(API_PARAM_DROPCOLUMNS, element);
baseDataValidator.reset().parameter(API_PARAM_DROPCOLUMNS).value(dropColumns).ignoreIfNull().jsonArrayNotEmpty();
if (dropColumns != null) {
for (final JsonElement column : dropColumns) {
this.fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), SUPPORTED_PARAMETERS_FOR_DROP_COLUMNS);
final String name = this.fromApiJsonHelper.extractStringNamed(NAME, column);
baseDataValidator.reset().parameter(NAME).value(name).notBlank().isNotOneOfTheseValues("id", fkColumnName)
.matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
final String name = this.fromApiJsonHelper.extractStringNamed(API_FIELD_NAME, column);
baseDataValidator.reset().parameter(API_FIELD_NAME).value(name).notBlank()
.isNotOneOfTheseValues(TABLE_FIELD_ID, fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN);
}
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateType(DataValidatorBuilder validator, final JsonElement column) {
final String type = this.fromApiJsonHelper.extractStringNamed(API_FIELD_TYPE, column);
validator.reset().parameter(API_FIELD_TYPE).value(type).notBlank().isOneOfTheseStringValues(SUPPORTED_COLUMN_TYPES);
if (type == null) {
return;
}
final Integer length = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(API_FIELD_LENGTH, column);
JdbcJavaType jdbcType = mapApiTypeToJdbcType(type);
validator = validator.reset().parameter(API_FIELD_LENGTH).value(length);
if (jdbcType.hasPrecision(databaseTypeResolver.databaseType())) {
if (jdbcType.isStringType() && length == null) {
validator.failWithCode("must.be.provided.when.type.is.String");
}
validator.ignoreIfNull().positiveAmount();
} // else, the precision is ignored
final String code = this.fromApiJsonHelper.extractStringNamed(API_FIELD_CODE, column);
if (type.equalsIgnoreCase(API_FIELD_TYPE_DROPDOWN)) {
if (code != null) {
validator.reset().parameter(API_FIELD_CODE).value(code).notBlank().matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
} else {
validator.reset().parameter(API_FIELD_CODE).value(code).cantBeBlankWhenParameterProvidedIs(API_FIELD_TYPE, type);
}
} else {
validator.reset().parameter(API_FIELD_CODE).value(code).mustBeBlankWhenParameterProvided(API_FIELD_TYPE, type);
}
}
@NotNull
public static JdbcJavaType mapApiTypeToJdbcType(@NotNull String apiType) {
return switch (apiType.toLowerCase()) {
case API_FIELD_TYPE_STRING -> VARCHAR;
case API_FIELD_TYPE_NUMBER, API_FIELD_TYPE_DROPDOWN -> INTEGER;
case API_FIELD_TYPE_BOOLEAN -> BOOLEAN;
case API_FIELD_TYPE_DECIMAL -> DECIMAL;
case API_FIELD_TYPE_DATE -> DATE;
case API_FIELD_TYPE_DATETIME, API_FIELD_TYPE_TIMESTAMP -> DATETIME;
case API_FIELD_TYPE_TEXT -> TEXT;
default -> throw new PlatformDataIntegrityException("error.msg.datatable.column.type.invalid",
"Column type " + apiType + " is not supported.");
};
}
private void validateEntitySubType(final DataValidatorBuilder baseDataValidator, final JsonElement element,
final EntityTables entityTable) {
if (entityTable == EntityTables.CLIENT) {
String entitySubType = this.fromApiJsonHelper.extractStringNamed(API_PARAM_SUBTYPE, element);
baseDataValidator.reset().parameter(API_PARAM_SUBTYPE).value(entitySubType).notBlank(); // Person or Entity
}
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -37,9 +37,6 @@ public final class DataTableApiConstant {
public static final String CREATEDAT_FIELD_NAME = "created_at";
public static final String UPDATEDAT_FIELD_NAME = "updated_at";
// Field Types
public static final String DATETIME_FIELD_TYPE = "DateTime";
// associationParameters
public static final String allAssociateParamName = "all";
public static final String repaymentScheduleAssociateParamName = "repaymentSchedule";
@@ -55,4 +52,38 @@ public final class DataTableApiConstant {
public static final String meetingAssociateParamName = "meeting";
public static final String emiAmountVariationsAssociateParamName = "emiAmountVariations";
public static final String collectionAssociateParamName = "collection";
public static final String TABLE_COLUMN_CODE_MAPPINGS = "x_table_column_code_mappings";
public static final String TABLE_REGISTERED_TABLE = "x_registered_table";
public static final String TABLE_FIELD_ID = "id";
public static final String API_PARAM_COLUMNS = "columns";
public static final String API_PARAM_CHANGECOLUMNS = "changeColumns";
public static final String API_PARAM_ADDCOLUMNS = "addColumns";
public static final String API_PARAM_DROPCOLUMNS = "dropColumns";
public static final String API_PARAM_DATATABLE_NAME = "datatableName";
public static final String API_PARAM_APPTABLE_NAME = "apptableName";
public static final String API_PARAM_SUBTYPE = "entitySubType";
public static final String API_PARAM_MULTIROW = "multiRow";
public static final String API_FIELD_NAME = "name";
public static final String API_FIELD_NEWNAME = "newName";
public static final String API_FIELD_TYPE = "type";
public static final String API_FIELD_LENGTH = "length";
public static final String API_FIELD_MANDATORY = "mandatory";
public static final String API_FIELD_UNIQUE = "unique";
public static final String API_FIELD_INDEXED = "indexed";
public static final String API_FIELD_AFTER = "after";
public static final String API_FIELD_CODE = "code";
public static final String API_FIELD_NEWCODE = "newCode";
public static final String API_FIELD_TYPE_STRING = "string";
public static final String API_FIELD_TYPE_NUMBER = "number";
public static final String API_FIELD_TYPE_BOOLEAN = "boolean";
public static final String API_FIELD_TYPE_DECIMAL = "decimal";
public static final String API_FIELD_TYPE_DATE = "date";
public static final String API_FIELD_TYPE_DATETIME = "datetime";
public static final String API_FIELD_TYPE_TIMESTAMP = "timestamp";
public static final String API_FIELD_TYPE_TEXT = "text";
public static final String API_FIELD_TYPE_DROPDOWN = "dropdown";
}
@@ -18,88 +18,114 @@
*/
package org.apache.fineract.infrastructure.dataqueries.data;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.ACTIVATE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.APPROVE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.CLOSE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.CREATE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.DISBURSE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.REJECTED;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.WITHDRAWN;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.WRITE_OFF;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public enum EntityTables {
CLIENT("m_client", ImmutableList.of(StatusEnum.CREATE.getCode(), StatusEnum.ACTIVATE.getCode(), StatusEnum.CLOSE.getCode()),
"client_id"), LOAN("m_loan",
ImmutableList.of(StatusEnum.CREATE.getCode(), StatusEnum.APPROVE.getCode(), StatusEnum.DISBURSE.getCode(),
StatusEnum.WITHDRAWN.getCode(), StatusEnum.REJECTED.getCode(), StatusEnum.WRITE_OFF.getCode()),
"loan_id"), GROUP("m_group",
ImmutableList.of(StatusEnum.CREATE.getCode(), StatusEnum.ACTIVATE.getCode(), StatusEnum.CLOSE.getCode()),
"group_id"), SAVING("m_savings_account",
ImmutableList.of(StatusEnum.CREATE.getCode(), StatusEnum.APPROVE.getCode(),
StatusEnum.ACTIVATE.getCode(), StatusEnum.WITHDRAWN.getCode(), StatusEnum.REJECTED.getCode(),
StatusEnum.CLOSE.getCode()),
"savings_account_id"), SAVINGS_TRANSACTION("m_savings_account_transaction", ImmutableList.of(),
"savings_account_transcation_id"), OFFICE("m_office", ImmutableList.of(),
"office_id"), PRODUCT_LOAN("m_product_loan", ImmutableList.of(),
"product_loan_id"), SAVINGS_PRODUCT("m_savings_product", ImmutableList.of(),
"savings_product_id"), SHARE_PRODUCT("m_share_product",
ImmutableList.of(), "share_product_id");
CLIENT("m_client", "client_id", "id", CREATE, ACTIVATE, CLOSE), //
GROUP("m_group", "group_id", "id", CREATE, ACTIVATE, CLOSE), //
CENTER("m_center", "m_group", "center_id", "id"), //
OFFICE("m_office", "office_id", "id"), //
LOAN_PRODUCT("m_product_loan", "product_loan_id", "id"), //
LOAN("m_loan", "loan_id", "id", CREATE, APPROVE, DISBURSE, WITHDRAWN, REJECTED, WRITE_OFF), //
SAVINGS_PRODUCT("m_savings_product", "savings_product_id", "id"), //
SAVINGS("m_savings_account", "savings_account_id", "id", CREATE, APPROVE, ACTIVATE, WITHDRAWN, REJECTED, CLOSE), //
SAVINGS_TRANSACTION("m_savings_account_transaction", "savings_transaction_id", "id"), //
SHARE_PRODUCT("m_share_product", "share_product_id", "id"), //
;
private static final Map<String, EntityTables> lookup = new HashMap<String, EntityTables>();
public static final EntityTables[] VALUES = values();
static {
for (EntityTables d : EntityTables.values()) {
lookup.put(d.getName(), d);
}
}
private static final List<String> ENTITY_NAMES = Arrays.stream(VALUES).map(EntityTables::getName).toList();
private static final Map<String, EntityTables> BY_ENTITY_NAME = Arrays.stream(VALUES)
.collect(Collectors.toMap(EntityTables::getName, e -> e));
private final String name;
private final ImmutableList<Integer> codes;
private final String apptableName;
private final String foreignKeyColumnNameOnDatatable;
private final String refColumn; // referenced column name on apptable
EntityTables(String name, ImmutableList<Integer> codes, String foreignKeyColumnNameOnDatatable) {
private final ImmutableList<StatusEnum> checkStatuses;
EntityTables(String name, String apptableName, String foreignKeyColumnNameOnDatatable, String refColumn, StatusEnum... statuses) {
this.name = name;
this.codes = codes;
this.apptableName = apptableName;
this.foreignKeyColumnNameOnDatatable = foreignKeyColumnNameOnDatatable;
this.refColumn = refColumn;
this.checkStatuses = statuses == null ? ImmutableList.of() : ImmutableList.copyOf(statuses);
}
public static List<String> getEntitiesList() {
List<String> data = new ArrayList<String>();
for (EntityTables entity : EntityTables.values()) {
data.add(entity.name);
}
return data;
}
public static ImmutableList<Integer> getStatus(String name) {
if (lookup.get(name) != null) {
return lookup.get(name).getCodes();
}
return ImmutableList.of();
}
public static EntityTables fromName(String name) {
return name == null ? null : lookup.get(name.toLowerCase());
}
public ImmutableList<Integer> getCodes() {
return this.codes;
EntityTables(String name, String foreignKeyColumnNameOnDatatable, String refColumn, StatusEnum... statuses) {
this(name, name, foreignKeyColumnNameOnDatatable, refColumn, statuses);
}
public String getName() {
return name;
}
public String getApptableName() {
return apptableName;
}
public String getForeignKeyColumnNameOnDatatable() {
return this.foreignKeyColumnNameOnDatatable;
}
public static String getForeignKeyColumnNameOnDatatable(String name) {
return lookup.get(name).foreignKeyColumnNameOnDatatable;
public String getRefColumn() {
return refColumn;
}
public List<StatusEnum> getCheckStatuses() {
return checkStatuses;
}
public boolean hasCheck() {
return checkStatuses != null && !checkStatuses.isEmpty();
}
public static List<String> getEntityNames() {
return ENTITY_NAMES;
}
public static EntityTables fromEntityName(String name) {
return name == null ? null : BY_ENTITY_NAME.get(name.toLowerCase());
}
public static String getForeignKeyColumnNameOnDatatable(String name) {
EntityTables entityTable = fromEntityName(name);
return entityTable == null ? null : entityTable.getForeignKeyColumnNameOnDatatable();
}
@NotNull
public static List<StatusEnum> getCheckStatuses(String name) {
EntityTables entityTable = fromEntityName(name);
return entityTable == null ? List.of() : entityTable.getCheckStatuses();
}
@NotNull
public static List<Integer> getCheckStatusCodes(String name) {
return getCheckStatuses(name).stream().map(StatusEnum::getCode).toList();
}
@NotNull
public static List<EntityTables> getFiltered(Predicate<EntityTables> filter) {
return Arrays.stream(VALUES).filter(filter).toList();
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.infrastructure.dataqueries.data;
import java.util.List;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
/**
* Immutable data object for generic resultset data.
@@ -41,16 +42,13 @@ public final class GenericResultsetData {
return this.data;
}
public String getColTypeOfColumnNamed(final String columnName) {
String colType = null;
public JdbcJavaType getColTypeOfColumnNamed(final String columnName) {
for (final ResultsetColumnHeaderData columnHeader : this.columnHeaders) {
if (columnHeader.isNamed(columnName)) {
colType = columnHeader.getColumnType();
return columnHeader.getColumnType();
}
}
return colType;
return null;
}
public boolean hasNoEntries() {
@@ -19,6 +19,7 @@
package org.apache.fineract.infrastructure.dataqueries.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
/**
* A {@link RuntimeException} thrown when datatable resources are not found.
@@ -32,4 +33,12 @@ public class DatatableNotFoundException extends AbstractPlatformResourceNotFound
public DatatableNotFoundException(final String datatable) {
super("error.msg.datatable.not.found", "Datatable not found.", datatable);
}
public DatatableNotFoundException(final EntityTables entityTable, final Long id) {
this(entityTable.getName(), id);
}
public DatatableNotFoundException(final EntityTables entityTable) {
this(entityTable.getName());
}
}
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_DATATABLE_NAME;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
@@ -41,7 +43,6 @@ import org.springframework.stereotype.Component;
public final class EntityDatatableChecksDataValidator {
public static final String ENTITY = "entity";
public static final String DATATABLE_NAME = "datatableName";
public static final String STATUS = "status";
public static final String SYSTEM_DEFINED = "systemDefined";
public static final String PRODUCT_ID = "productId";
@@ -50,7 +51,7 @@ public final class EntityDatatableChecksDataValidator {
* The parameters supported for this command.
*/
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(
Arrays.asList(ENTITY, DATATABLE_NAME, STATUS, SYSTEM_DEFINED, PRODUCT_ID));
Arrays.asList(ENTITY, API_PARAM_DATATABLE_NAME, STATUS, SYSTEM_DEFINED, PRODUCT_ID));
private final FromJsonHelper fromApiJsonHelper;
@Autowired
@@ -74,15 +75,15 @@ public final class EntityDatatableChecksDataValidator {
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String entity = this.fromApiJsonHelper.extractStringNamed(ENTITY, element);
baseDataValidator.reset().parameter(ENTITY).value(entity).notBlank().isOneOfTheseStringValues(EntityTables.getEntitiesList());
baseDataValidator.reset().parameter(ENTITY).value(entity).notBlank().isOneOfTheseStringValues(EntityTables.getEntityNames());
final Integer status = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(STATUS, element);
final Object[] entityTablesStatuses = EntityTables.getStatus(entity).toArray();
final Object[] entityTablesStatuses = EntityTables.getCheckStatusCodes(entity).toArray();
baseDataValidator.reset().parameter(STATUS).value(status).isOneOfTheseValues(entityTablesStatuses);
final String datatableName = this.fromApiJsonHelper.extractStringNamed(DATATABLE_NAME, element);
baseDataValidator.reset().parameter(DATATABLE_NAME).value(datatableName).notBlank();
final String datatableName = this.fromApiJsonHelper.extractStringNamed(API_PARAM_DATATABLE_NAME, element);
baseDataValidator.reset().parameter(API_PARAM_DATATABLE_NAME).value(datatableName).notBlank();
if (this.fromApiJsonHelper.parameterExists(SYSTEM_DEFINED, element)) {
final String systemDefined = this.fromApiJsonHelper.extractStringNamed(SYSTEM_DEFINED, element);
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import com.google.common.collect.ImmutableList;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -30,6 +29,7 @@ import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableCheckStatusData;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableChecksData;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
@@ -52,6 +52,7 @@ import org.springframework.stereotype.Service;
public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatatableChecksReadService {
private final JdbcTemplate jdbcTemplate;
protected final DatabaseTypeResolver databaseTypeResolver;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final RegisterDataTableMapper registerDataTableMapper;
private final EntityDataTableChecksMapper entityDataTableChecksMapper;
@@ -62,14 +63,14 @@ public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatat
private final PaginationHelper paginationHelper;
@Autowired
public EntityDatatableChecksReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate,
final LoanProductReadPlatformService loanProductReadPlatformService,
public EntityDatatableChecksReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseTypeResolver databaseTypeResolver,
DatabaseSpecificSQLGenerator sqlGenerator, final LoanProductReadPlatformService loanProductReadPlatformService,
final SavingsProductReadPlatformService savingsProductReadPlatformService,
final EntityDatatableChecksRepository entityDatatableChecksRepository,
final ReadWriteNonCoreDataService readWriteNonCoreDataService, DatabaseSpecificSQLGenerator sqlGenerator,
PaginationHelper paginationHelper) {
final ReadWriteNonCoreDataService readWriteNonCoreDataService, PaginationHelper paginationHelper) {
this.jdbcTemplate = jdbcTemplate;
this.databaseTypeResolver = databaseTypeResolver;
this.sqlGenerator = sqlGenerator;
this.registerDataTableMapper = new RegisterDataTableMapper();
this.entityDataTableChecksMapper = new EntityDataTableChecksMapper();
@@ -141,28 +142,25 @@ public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatat
@Override
public EntityDataTableChecksTemplateData retrieveTemplate() {
List<DatatableChecksData> dataTables = getDataTables();
List<String> entities = EntityTables.getEntitiesList();
List<DatatableCheckStatusData> statusClient = getStatusList(EntityTables.getStatus("m_client"));
List<DatatableCheckStatusData> statusLoan = getStatusList(EntityTables.getStatus("m_loan"));
List<DatatableCheckStatusData> statusGroup = getStatusList(EntityTables.getStatus("m_group"));
List<DatatableCheckStatusData> statusSavings = getStatusList(EntityTables.getStatus("m_savings_account"));
List<String> entities = EntityTables.getEntityNames();
List<DatatableCheckStatusData> clientStatuses = getStatusList(EntityTables.CLIENT.getCheckStatuses());
List<DatatableCheckStatusData> loanStatuses = getStatusList(EntityTables.LOAN.getCheckStatuses());
List<DatatableCheckStatusData> groupstatuses = getStatusList(EntityTables.GROUP.getCheckStatuses());
List<DatatableCheckStatusData> savingsStatuses = getStatusList(EntityTables.SAVINGS.getCheckStatuses());
Collection<LoanProductData> loanProductDatas = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(true);
Collection<SavingsProductData> savingsProductDatas = this.savingsProductReadPlatformService.retrieveAllForLookup();
return new EntityDataTableChecksTemplateData(entities, statusClient, statusGroup, statusSavings, statusLoan, dataTables,
return new EntityDataTableChecksTemplateData(entities, clientStatuses, groupstatuses, savingsStatuses, loanStatuses, dataTables,
loanProductDatas, savingsProductDatas);
}
private List<DatatableCheckStatusData> getStatusList(ImmutableList<Integer> statuses) {
private List<DatatableCheckStatusData> getStatusList(List<StatusEnum> statuses) {
List<DatatableCheckStatusData> ret = new ArrayList<>();
if (statuses != null) {
for (Integer status : statuses) {
StatusEnum statusEnum = StatusEnum.fromInt(status);
ret.add(new DatatableCheckStatusData(statusEnum.name(), statusEnum.getCode()));
for (StatusEnum status : statuses) {
ret.add(new DatatableCheckStatusData(status.name(), status.getCode()));
}
}
return ret;
@@ -69,7 +69,6 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
@Transactional
@Override
public CommandProcessingResult createCheck(final JsonCommand command) {
try {
this.context.authenticatedUser();
@@ -105,11 +104,11 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
throw new EntityDatatableCheckAlreadyExistsException(entity, status, datatableName);
}
} else {
EntityTables entityTable = EntityTables.fromName(entity);
if (EntityTables.LOAN == entityTable) {
EntityTables entityTable = EntityTables.fromEntityName(entity);
if (entityTable == EntityTables.LOAN) {
// if invalid loan product id, throws exception
this.loanProductReadPlatformService.retrieveLoanProduct(productId);
} else if (EntityTables.SAVING == entityTable) {
} else if (entityTable == EntityTables.SAVINGS) {
// if invalid savings product id, throws exception
this.savingsProductReadPlatformService.retrieveOne(productId);
} else {
@@ -21,20 +21,23 @@ package org.apache.fineract.infrastructure.dataqueries.service;
import java.util.List;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
public interface GenericDataService {
GenericResultsetData fillGenericResultSet(String sql);
List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(String tableName);
List<ResultsetRowData> fillResultsetRowData(String sql, List<ResultsetColumnHeaderData> columnHeaders);
String generateJsonFromGenericResultsetData(GenericResultsetData grs);
String replace(String str, String pattern, String replace);
String wrapSQL(String sql);
List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(String datatable);
boolean isExplicitlyUnique(String tableName, String columnName);
boolean isExplicitlyUnique(String datatable, String columnName);
boolean isExplicitlyIndexed(String datatable, String columnName);
boolean isExplicitlyIndexed(String tableName, String columnName);
}
@@ -19,23 +19,35 @@
package org.apache.fineract.infrastructure.dataqueries.service;
import static java.lang.String.format;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DATE;
import static org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DATETIME;
import static org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData.DisplayType.CODELOOKUP;
import static org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData.DisplayType.DECIMAL;
import static org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData.DisplayType.INTEGER;
import static org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData.DisplayType.TIME;
import java.sql.Date;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseIndependentQueryService;
import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.core.service.database.IndexDetail;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
import org.apache.fineract.infrastructure.core.service.database.RoutingDataSource;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnValueData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
import org.jetbrains.annotations.NotNull;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
@@ -51,6 +63,7 @@ public class GenericDataServiceImpl implements GenericDataService {
private final RoutingDataSource dataSource;
private final DatabaseIndependentQueryService databaseIndependentQueryService;
private final DatatableKeywordGenerator datatableKeywordGenerator;
private final DatabaseTypeResolver databaseTypeResolver;
@Override
public GenericResultsetData fillGenericResultSet(final String sql) {
@@ -58,38 +71,18 @@ public class GenericDataServiceImpl implements GenericDataService {
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); // NOSONAR
final List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
final List<ResultsetRowData> resultsetDataRows = new ArrayList<>();
final SqlRowSetMetaData rsmd = rs.getMetaData();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
final String columnName = rsmd.getColumnName(i + 1);
final String columnType = rsmd.getColumnTypeName(i + 1);
final ResultsetColumnHeaderData columnHeader = ResultsetColumnHeaderData.basic(columnName, columnType);
final ResultsetColumnHeaderData columnHeader = ResultsetColumnHeaderData.basic(columnName, columnType,
databaseTypeResolver.databaseType());
columnHeaders.add(columnHeader);
}
while (rs.next()) {
final List<Object> columnValues = new ArrayList<>();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
final String columnName = rsmd.getColumnName(i + 1);
final String colType = columnHeaders.get(i).getColumnType();
if ("DATE".equalsIgnoreCase(colType)) {
java.sql.Date tmpDate = (java.sql.Date) rs.getObject(columnName);
columnValues.add(tmpDate != null ? tmpDate.toLocalDate() : null);
} else if ("TIMESTAMP WITHOUT TIME ZONE".equalsIgnoreCase(colType) // PostgreSQL
|| "DATETIME".equalsIgnoreCase(colType) || "TIMESTAMP".equalsIgnoreCase(colType)) {
Timestamp tmpDate = (Timestamp) rs.getObject(columnName);
columnValues.add(tmpDate != null ? tmpDate.toLocalDateTime() : null);
} else {
columnValues.add(rs.getObject(columnName));
}
}
final ResultsetRowData resultsetDataRow = ResultsetRowData.create(columnValues);
resultsetDataRows.add(resultsetDataRow);
}
final List<ResultsetRowData> resultsetDataRows = fillResultsetRowData(rs, columnHeaders);
return new GenericResultsetData(columnHeaders, resultsetDataRows);
} catch (DataAccessException e) {
@@ -98,6 +91,80 @@ public class GenericDataServiceImpl implements GenericDataService {
}
}
@Override
public List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(final String tableName) {
final SqlRowSet columnDefinitions = getTableMetaData(tableName);
final List<IndexDetail> indexDefinitions = getDatatableIndexData(tableName);
DatabaseType dialect = databaseTypeResolver.databaseType();
final List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
columnDefinitions.beforeFirst();
while (columnDefinitions.next()) {
final String columnName = columnDefinitions.getString(1);
final String isNullable = columnDefinitions.getString(2);
final String isPrimaryKey = columnDefinitions.getString(5);
final String columnType = columnDefinitions.getString(3);
final Long columnLength = columnDefinitions.getLong(4);
final boolean columnNullable = "YES".equalsIgnoreCase(isNullable) || "TRUE".equalsIgnoreCase(isNullable);
final boolean columnIsPrimaryKey = "PRI".equalsIgnoreCase(isPrimaryKey) || "TRUE".equalsIgnoreCase(isPrimaryKey);
// primary keys are automatically unique
final boolean columnIsUnique = columnIsPrimaryKey || isExplicitlyUnique(tableName, columnName, indexDefinitions);
// primary keys and unique constrained columns are automatically indexed
final boolean columnIsIndexed = columnIsPrimaryKey || columnIsUnique
|| isExplicitlyIndexed(tableName, columnName, indexDefinitions);
JdbcJavaType jdbcType = JdbcJavaType.getByTypeName(dialect, columnType);
List<ResultsetColumnValueData> columnValues = new ArrayList<>();
String codeName = null;
final int codePosition = columnName.indexOf("_cd");
if (codePosition > 0 && jdbcType != null && (jdbcType.isVarcharType() || jdbcType.isIntegerType())) {
codeName = columnName.substring(0, codePosition);
columnValues = retrieveCodeValues(codeName);
}
columnHeaders.add(ResultsetColumnHeaderData.detailed(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey,
columnValues, codeName, columnIsUnique, columnIsIndexed, dialect));
}
return columnHeaders;
}
@NotNull
@Override
public List<ResultsetRowData> fillResultsetRowData(final String sql, List<ResultsetColumnHeaderData> columnHeaders) {
final SqlRowSet rs = jdbcTemplate.queryForRowSet(sql); // NOSONAR
return fillResultsetRowData(rs, columnHeaders);
}
@NotNull
private static List<ResultsetRowData> fillResultsetRowData(SqlRowSet rs, List<ResultsetColumnHeaderData> columnHeaders) {
final SqlRowSetMetaData rsmd = rs.getMetaData();
final List<ResultsetRowData> resultsetDataRows = new ArrayList<>();
while (rs.next()) {
final List<Object> columnValues = new ArrayList<>();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
final String columnName = rsmd.getColumnName(i + 1);
final JdbcJavaType colType = columnHeaders.get(i).getColumnType();
if (colType == DATE) {
Date tmpDate = (Date) rs.getObject(columnName);
columnValues.add(tmpDate == null ? null : tmpDate.toLocalDate());
} else if (colType == DATETIME) {
Object tmpDate = rs.getObject(columnName);
columnValues.add(
tmpDate == null ? null : (tmpDate instanceof Timestamp ? ((Timestamp) tmpDate).toLocalDateTime() : tmpDate));
} else {
columnValues.add(rs.getObject(columnName));
}
}
resultsetDataRows.add(ResultsetRowData.create(columnValues));
}
return resultsetDataRows;
}
@Override
public String replace(final String str, final String pattern, final String replace) {
// JPW - this replace may / may not be any better or quicker than the
@@ -129,7 +196,6 @@ public class GenericDataServiceImpl implements GenericDataService {
@Override
public String generateJsonFromGenericResultsetData(final GenericResultsetData grs) {
final StringBuilder writer = new StringBuilder();
writer.append("[");
@@ -141,7 +207,7 @@ public class GenericDataServiceImpl implements GenericDataService {
Integer rSize;
final String doubleQuote = "\"";
final String slashDoubleQuote = "\\\"";
String currColType;
ResultsetColumnHeaderData.DisplayType colDisplayType;
Object currVal;
for (int i = 0; i < data.size(); i++) {
@@ -150,39 +216,31 @@ public class GenericDataServiceImpl implements GenericDataService {
row = data.get(i).getRow();
rSize = row.size();
for (int j = 0; j < rSize; j++) {
writer.append(doubleQuote + columnHeaders.get(j).getColumnName() + doubleQuote + ": ");
currColType = columnHeaders.get(j).getColumnDisplayType();
final String colType = columnHeaders.get(j).getColumnType();
if (currColType == null && colType.equalsIgnoreCase("INT")) {
currColType = "INTEGER";
}
if (currColType == null && colType.equalsIgnoreCase("VARCHAR")) {
currColType = "VARCHAR";
}
if (currColType == null && colType.equalsIgnoreCase("DATE")) {
currColType = "DATE";
}
if (currColType == null && colType.equalsIgnoreCase("DATETIME")) {
currColType = "DATETIME";
ResultsetColumnHeaderData columnHeader = columnHeaders.get(j);
writer.append(doubleQuote + columnHeader.getColumnName() + doubleQuote + ": ");
colDisplayType = columnHeader.getColumnDisplayType();
final JdbcJavaType colType = columnHeader.getColumnType();
if (colDisplayType == null) {
colDisplayType = ResultsetColumnHeaderData.calcColumnDisplayType(colType);
}
currVal = row.get(j);
if (currVal != null && currColType != null) {
if (currColType.equalsIgnoreCase("DECIMAL") || currColType.equalsIgnoreCase("INTEGER")
|| currColType.equalsIgnoreCase("CODELOOKUP")) {
if (currVal != null && colDisplayType != null) {
if (colDisplayType == ResultsetColumnHeaderData.DisplayType.DATE) {
final LocalDate localDate = (LocalDate) currVal;
writer.append(format("[%d,%d,%d]", localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()));
} else if (colDisplayType == ResultsetColumnHeaderData.DisplayType.DATETIME) {
final LocalDateTime localDateTime = (LocalDateTime) currVal;
writer.append(format("[%d,%d,%d,%d,%d,%d,%d]", localDateTime.getYear(), localDateTime.getMonthValue(),
localDateTime.getDayOfMonth(), localDateTime.getHour(), localDateTime.getMinute(),
localDateTime.getSecond(), localDateTime.getNano()));
} else if (colDisplayType == TIME) {
final LocalTime localTime = (LocalTime) currVal;
writer.append(format("[%d,%d,%d,%d]", localTime.getHour(), localTime.getMinute(), localTime.getSecond(),
localTime.getNano()));
} else if (colDisplayType == DECIMAL || colDisplayType == INTEGER || colDisplayType == CODELOOKUP) {
writer.append(currVal);
} else {
if (currColType.equalsIgnoreCase("DATE")) {
final LocalDate localDate = (LocalDate) currVal;
writer.append(format("[%d,%d,%d]", localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()));
} else if (currColType.equalsIgnoreCase("DATETIME")) {
final LocalDateTime localDateTime = (LocalDateTime) currVal;
writer.append(format("[%d,%d,%d,%d,%d,%d,%d]", localDateTime.getYear(), localDateTime.getMonthValue(),
localDateTime.getDayOfMonth(), localDateTime.getHour(), localDateTime.getMinute(),
localDateTime.getSecond(), localDateTime.getNano()));
} else {
writer.append(doubleQuote + replace(String.valueOf(currVal), doubleQuote, slashDoubleQuote) + doubleQuote);
}
writer.append(doubleQuote + replace(String.valueOf(currVal), doubleQuote, slashDoubleQuote) + doubleQuote);
}
} else {
writer.append("null");
@@ -201,69 +259,26 @@ public class GenericDataServiceImpl implements GenericDataService {
writer.append("\n]");
return writer.toString();
}
@Override
public List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(final String datatable) {
final SqlRowSet columnDefinitions = getDatatableMetaData(datatable);
final List<IndexDetail> indexDefinitions = getDatatableIndexData(datatable);
final List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
columnDefinitions.beforeFirst();
while (columnDefinitions.next()) {
final String columnName = columnDefinitions.getString(1);
final String isNullable = columnDefinitions.getString(2);
final String isPrimaryKey = columnDefinitions.getString(5);
final String columnType = columnDefinitions.getString(3);
final Long columnLength = columnDefinitions.getLong(4);
final boolean columnNullable = "YES".equalsIgnoreCase(isNullable) || "TRUE".equalsIgnoreCase(isNullable);
final boolean columnIsPrimaryKey = "PRI".equalsIgnoreCase(isPrimaryKey) || "TRUE".equalsIgnoreCase(isPrimaryKey);
// primary keys are automatically unique
final boolean columnIsUnique = columnIsPrimaryKey || isExplicitlyUnique(datatable, columnName, indexDefinitions);
// primary keys and unique constrained columns are automatically indexed
final boolean columnIsIndexed = columnIsPrimaryKey || columnIsUnique
|| isExplicitlyIndexed(datatable, columnName, indexDefinitions);
List<ResultsetColumnValueData> columnValues = new ArrayList<>();
String codeName = null;
final int codePosition = columnName.indexOf("_cd");
if ("varchar".equalsIgnoreCase(columnType) || "int".equalsIgnoreCase(columnType) || "integer".equalsIgnoreCase(columnType)) {
if (codePosition > 0) {
codeName = columnName.substring(0, codePosition);
columnValues = retrieveColumnValues(codeName);
}
}
columnHeaders.add(ResultsetColumnHeaderData.detailed(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey,
columnValues, codeName, columnIsUnique, columnIsIndexed));
}
return columnHeaders;
}
private boolean isExplicitlyUnique(String datatable, String columnName, List<IndexDetail> indexDefinitions) {
String keyNameToCheck = datatableKeywordGenerator.generateUniqueKeyName(datatable, columnName);
private boolean isExplicitlyUnique(String tableName, String columnName, List<IndexDetail> indexDefinitions) {
String keyNameToCheck = datatableKeywordGenerator.generateUniqueKeyName(tableName, columnName);
return checkKeyPresent(keyNameToCheck, indexDefinitions);
}
@Override
public boolean isExplicitlyUnique(String datatable, String columnName) {
return isExplicitlyUnique(datatable, columnName, getDatatableIndexData(datatable));
public boolean isExplicitlyUnique(String tableName, String columnName) {
return isExplicitlyUnique(tableName, columnName, getDatatableIndexData(tableName));
}
private boolean isExplicitlyIndexed(String datatable, String columnName, List<IndexDetail> indexDefinitions) {
String keyNameToCheck = datatableKeywordGenerator.generateIndexName(datatable, columnName);
private boolean isExplicitlyIndexed(String tableName, String columnName, List<IndexDetail> indexDefinitions) {
String keyNameToCheck = datatableKeywordGenerator.generateIndexName(tableName, columnName);
return checkKeyPresent(keyNameToCheck, indexDefinitions);
}
@Override
public boolean isExplicitlyIndexed(String datatable, String columnName) {
return isExplicitlyIndexed(datatable, columnName, getDatatableIndexData(datatable));
public boolean isExplicitlyIndexed(String tableName, String columnName) {
return isExplicitlyIndexed(tableName, columnName, getDatatableIndexData(tableName));
}
private boolean checkKeyPresent(String keyNameToCheck, List<IndexDetail> indexDefinitions) {
@@ -276,25 +291,23 @@ public class GenericDataServiceImpl implements GenericDataService {
}
@SuppressWarnings("AvoidHidingCauseException")
private List<IndexDetail> getDatatableIndexData(String datatable) {
private List<IndexDetail> getDatatableIndexData(String tableName) {
try {
return databaseIndependentQueryService.getTableIndexes(dataSource, datatable);
return databaseIndependentQueryService.getTableIndexes(dataSource, tableName);
} catch (IllegalArgumentException e) {
throw new DatatableNotFoundException(datatable);
throw new DatatableNotFoundException(tableName);
}
}
/*
* Candidate for using caching there to get allowed 'column values' from code/codevalue tables
*/
private List<ResultsetColumnValueData> retrieveColumnValues(final String codeName) {
private List<ResultsetColumnValueData> retrieveCodeValues(final String codeName) {
final String sql = "select v.id, v.code_score, v.code_value from m_code m join m_code_value v on v.code_id = m.id where m.code_name = ? order by v.order_position, v.id";
final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql, codeName); // NOSONAR
final List<ResultsetColumnValueData> columnValues = new ArrayList<>();
final String sql = "select v.id, v.code_score, v.code_value from m_code m join m_code_value v on v.code_id = m.id where m.code_name = ? order by v.order_position, v.id";
final SqlRowSet rsValues = this.jdbcTemplate.queryForRowSet(sql, new Object[] { codeName }); // NOSONAR
rsValues.beforeFirst();
while (rsValues.next()) {
final Integer id = rsValues.getInt("id");
@@ -308,11 +321,11 @@ public class GenericDataServiceImpl implements GenericDataService {
}
@SuppressWarnings("AvoidHidingCauseException")
private SqlRowSet getDatatableMetaData(final String datatable) {
private SqlRowSet getTableMetaData(final String tableName) {
try {
return databaseIndependentQueryService.getTableColumns(dataSource, datatable);
return databaseIndependentQueryService.getTableColumns(dataSource, tableName);
} catch (IllegalArgumentException e) {
throw new DatatableNotFoundException(datatable);
throw new DatatableNotFoundException(tableName);
}
}
}
@@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
import org.apache.fineract.infrastructure.dataqueries.data.ReportParameterData;
@@ -230,7 +231,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
table.completeRow();
Integer rSize;
String currColType;
JdbcJavaType currColType;
String currVal;
log.debug("NO. of Rows: {}", data.size());
for (ResultsetRowData element : data) {
@@ -240,9 +241,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
currColType = columnHeaders.get(j).getColumnType();
currVal = (String) row.get(j);
if (currVal != null) {
if (currColType.equals("DECIMAL") || currColType.equals("DOUBLE") || currColType.equals("BIGINT")
|| currColType.equals("SMALLINT") || currColType.equals("INT")) {
if (currColType.isNumericType()) {
table.addCell(currVal.toString());
} else {
table.addCell(currVal.toString());
@@ -22,7 +22,7 @@ import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
@@ -30,7 +30,7 @@ import java.util.Arrays;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
public class MoneyData {
@@ -48,6 +48,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.interoperation.data.InteropAccountData;
@@ -76,7 +77,6 @@ import org.apache.fineract.interoperation.exception.InteropTransferAlreadyCommit
import org.apache.fineract.interoperation.exception.InteropTransferAlreadyOnHoldException;
import org.apache.fineract.interoperation.exception.InteropTransferMissingException;
import org.apache.fineract.interoperation.serialization.InteropDataValidator;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.exception.MultiException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
@@ -63,7 +64,6 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionRecoveryPaymentPostBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionRecoveryPaymentPreBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
@@ -27,7 +27,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
@@ -1230,7 +1230,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
}
final List<DatatableData> datatableTemplates = this.entityDatatableChecksReadService
.retrieveTemplates(StatusEnum.CREATE.getCode().longValue(), EntityTables.SAVING.getName(), productId);
.retrieveTemplates(StatusEnum.CREATE.getCode().longValue(), EntityTables.SAVINGS.getName(), productId);
template.setDatatables(datatableTemplates);
return template;
@@ -1324,7 +1324,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
sqlBuilder.append(" tr.release_id_of_hold_amount as releaseTransactionId,");
sqlBuilder.append(" tr.reason_for_block as reasonForBlock,");
sqlBuilder.append(" tr.submitted_on_date as submittedOnDate,");
sqlBuilder.append("tr.submitted_on_date as submittedOnDate,");
sqlBuilder.append(" au.username as submittedByUsername, ");
sqlBuilder.append(" nt.note as transactionNote, ");
sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,");
@@ -207,8 +207,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Map<String, Object> changes = account.activate(user, command, DateUtils.getBusinessLocalDate());
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
StatusEnum.ACTIVATE.getCode().longValue(), EntityTables.SAVING.getForeignKeyColumnNameOnDatatable(), account.productId());
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVINGS.getName(),
StatusEnum.ACTIVATE.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(), account.productId());
if (!changes.isEmpty()) {
final Locale locale = command.extractLocale();
@@ -924,8 +924,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
throw new SavingsAccountClosingNotAllowedException("linked", defaultUserMessage, savingsId);
}
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
StatusEnum.CLOSE.getCode().longValue(), EntityTables.SAVING.getForeignKeyColumnNameOnDatatable(), account.productId());
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVINGS.getName(),
StatusEnum.CLOSE.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(), account.productId());
final boolean isWithdrawBalance = command.booleanPrimitiveValueOfParameterNamed(withdrawBalanceParamName);
@@ -1517,9 +1517,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
/***
* Only recurring fees are allowed to inactivate
*/
// Only recurring fees are allowed to inactivate
if (!savingsAccountCharge.isRecurringFee()) {
baseDataValidator.reset().parameter(null).value(savingsAccountCharge.getId())
.failWithCodeNoParameterAddedToErrorCode("charge.inactivation.allowed.only.for.recurring.charges");
@@ -1547,9 +1545,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
chargePayments.add(transaction);
}
}
/***
* Reverse the excess payments of charge transactions
*/
// Reverse the excess payments of charge transactions
SavingsAccountTransaction lastChargePayment = getLastChargePayment(chargePayments);
this.undoTransaction(savingsAccountCharge.savingsAccount().getId(), lastChargePayment.getId(), false);
updatedCharge = account.getUpdatedChargeDetails(savingsAccountCharge);
@@ -257,11 +257,12 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final Long savingsId = account.getId();
if (command.parameterExists(SavingsApiConstants.datatables)) {
this.entityDatatableChecksWritePlatformService.saveDatatables(StatusEnum.CREATE.getCode().longValue(),
EntityTables.SAVING.getName(), savingsId, account.productId(),
EntityTables.SAVINGS.getName(), savingsId, account.productId(),
command.arrayOfParameterNamed(SavingsApiConstants.datatables));
}
this.entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
StatusEnum.CREATE.getCode().longValue(), EntityTables.SAVING.getForeignKeyColumnNameOnDatatable(), account.productId());
this.entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVINGS.getName(),
StatusEnum.CREATE.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(),
account.productId());
businessEventNotifierService.notifyPostBusinessEvent(new SavingsCreateBusinessEvent(account));
@@ -475,8 +476,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
StatusEnum.APPROVE.getCode().longValue(), EntityTables.SAVING.getForeignKeyColumnNameOnDatatable(),
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVINGS.getName(),
StatusEnum.APPROVE.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(),
savingsAccount.productId());
final Map<String, Object> changes = savingsAccount.approveApplication(currentUser, command, DateUtils.getBusinessLocalDate());
@@ -598,8 +599,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
StatusEnum.REJECTED.getCode().longValue(), EntityTables.SAVING.getForeignKeyColumnNameOnDatatable(),
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVINGS.getName(),
StatusEnum.REJECTED.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(),
savingsAccount.productId());
final Map<String, Object> changes = savingsAccount.rejectApplication(currentUser, command, DateUtils.getBusinessLocalDate());
@@ -635,8 +636,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
StatusEnum.WITHDRAWN.getCode().longValue(), EntityTables.SAVING.getForeignKeyColumnNameOnDatatable(),
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVINGS.getName(),
StatusEnum.WITHDRAWN.getCode().longValue(), EntityTables.SAVINGS.getForeignKeyColumnNameOnDatatable(),
savingsAccount.productId());
final Map<String, Object> changes = savingsAccount.applicantWithdrawsFromApplication(currentUser, command,
@@ -21,7 +21,11 @@ package org.apache.fineract.portfolio.search;
import java.util.HashSet;
import java.util.Set;
public class SearchConstants {
public final class SearchConstants {
public static final String API_PARAM_COLUMN = "column";
private SearchConstants() {}
public enum SearchResponseParameters {
@@ -0,0 +1,237 @@
/**
* 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.portfolio.search.service;
import static java.util.Locale.ENGLISH;
import static org.apache.fineract.infrastructure.core.data.ApiParameterError.parameterErrorWithValue;
import static org.apache.fineract.infrastructure.core.service.DateUtils.DEFAULT_DATETIME_FORMAT;
import static org.apache.fineract.infrastructure.core.service.DateUtils.DEFAULT_DATE_FORMAT;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_MANDATORY;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_COLUMN;
import com.google.common.base.Predicate;
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
import org.springframework.jdbc.support.rowset.SqlRowSet;
public final class SearchUtil {
public static final int DEFAULT_PAGE_SIZE = 50;
private static final JsonParserHelper helper = new JsonParserHelper();
private SearchUtil() {}
@NotNull
public static Map<String, ResultsetColumnHeaderData> mapHeadersToName(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders) {
return columnHeaders.stream().collect(Collectors.toMap(ResultsetColumnHeaderData::getColumnName, e -> e));
}
public static ResultsetColumnHeaderData findFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
@NotNull Predicate<ResultsetColumnHeaderData> filter) {
return columnHeaders.stream().filter(filter).findFirst().orElse(null);
}
public static ResultsetColumnHeaderData getFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
@NotNull Predicate<ResultsetColumnHeaderData> filter) {
ResultsetColumnHeaderData filtered = findFiltered(columnHeaders, filter);
if (filtered == null) {
throw new PlatformDataIntegrityException("error.msg.column.not.exists", "Column filtered does not exist");
}
return filtered;
}
public static void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull List<String> selectColumns,
@NotNull List<String> resultColumns, @NotNull List<JsonObject> results) {
JsonObject json = new JsonObject();
for (int i = 0; i < selectColumns.size(); i++) {
String sCol = selectColumns.get(i).replaceAll("\"", "");
Object rowValue = rowSet.getObject(sCol);
if (rowValue != null) {
String rCol = resultColumns.get(i);
if (rowValue instanceof Character) {
json.addProperty(rCol, (Character) rowValue);
} else if (rowValue instanceof Number) {
json.addProperty(rCol, new BigDecimal(rowValue.toString()));
} else if (rowValue instanceof Boolean) {
json.addProperty(rCol, (Boolean) rowValue);
} else if (rowValue instanceof LocalDateTime) {
json.addProperty(rCol, DateUtils.format((LocalDateTime) rowValue));
} else if (rowValue instanceof Timestamp) {
json.addProperty(rCol, DateUtils.format(((Timestamp) rowValue).toLocalDateTime()));
} else if (rowValue instanceof LocalDate) {
json.addProperty(rCol, DateUtils.format((LocalDate) rowValue));
} else if (rowValue instanceof Date) {
json.addProperty(rCol, DateUtils.format(((Date) rowValue).toLocalDate()));
} else {
json.addProperty(rCol, rowValue.toString());
}
}
}
if (json.size() > 0) {
results.add(json);
}
}
public static List<String> validateToJdbcColumns(List<String> columns, Map<String, ResultsetColumnHeaderData> columnHeaders,
List<ApiParameterError> errors, boolean allowEmpty) {
List<String> result = new ArrayList<>();
if (columns == null || columns.isEmpty()) {
if (!allowEmpty) {
errors.add(parameterErrorWithValue("error.msg.columns.empty", "Columns list is empty", API_PARAM_COLUMN, null));
}
} else {
columns.forEach(rcn -> result.add(validateToJdbcColumn(rcn, columnHeaders, errors, allowEmpty)));
}
return result;
}
public static String validateToJdbcColumn(String column, Map<String, ResultsetColumnHeaderData> columnHeaders,
List<ApiParameterError> errors, boolean allowEmpty) {
if (!allowEmpty && column == null) {
errors.add(parameterErrorWithValue("error.msg.column.empty", "Column filter is empty", API_PARAM_COLUMN, null));
}
if (column != null) {
SQLInjectionValidator.validateDynamicQuery(column);
if (!columnHeaders.containsKey(column)) {
column = camelToSnake(column);
if (!columnHeaders.containsKey(column)) {
errors.add(
parameterErrorWithValue("error.msg.invalid.column", "Column not exist in database", API_PARAM_COLUMN, column));
}
}
}
return column;
}
public static Object parseAndValidateJdbcColumnValue(String column, String columnValue,
Map<String, ResultsetColumnHeaderData> columnHeaders, DatabaseType dialect) {
List<ApiParameterError> errors = new ArrayList<>();
column = validateToJdbcColumn(column, columnHeaders, errors, false);
if (!errors.isEmpty()) {
throw new PlatformApiDataValidationException(errors);
}
ResultsetColumnHeaderData columnHeader = columnHeaders.get(column);
String dateFormat = columnHeader.getColumnType().isDateTimeType() ? DEFAULT_DATETIME_FORMAT : DEFAULT_DATE_FORMAT;
return columnHeader.getColumnType().toJdbcValue(dialect,
parseAndValidateColumnValue(columnHeader, columnValue, dateFormat, ENGLISH, dialect), false);
}
public static Object parseAndValidateColumnValue(final ResultsetColumnHeaderData columnHeader, final String pValue,
final String dateFormat, final Locale locale, DatabaseType dialect) {
String columnValue = pValue;
JdbcJavaType colType = columnHeader.getColumnType();
if (!colType.isStringType() || !columnHeader.isMandatory()) {
columnValue = StringUtils.trimToNull(columnValue);
}
String errorCode = "validation.msg.validation.errors.exist";
String errorMsg = "Validation errors exist.";
if (columnValue == null && columnHeader.isMandatory()) {
ApiParameterError error = ApiParameterError.parameterError("error.msg.column.mandatory", API_FIELD_MANDATORY,
columnHeader.getColumnName());
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
if (StringUtils.isEmpty(columnValue)) {
return columnValue;
}
SQLInjectionValidator.validateDynamicQuery(columnValue);
if (columnHeader.hasColumnValues()) {
if (columnHeader.isCodeValueDisplayType()) {
if (!columnHeader.isColumnValueAllowed(columnValue)) {
ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
return columnValue;
} else if (columnHeader.isCodeLookupDisplayType()) {
final Integer codeLookup = Integer.valueOf(columnValue);
if (!columnHeader.isColumnCodeAllowed(codeLookup)) {
ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
return codeLookup;
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.columnType.", "Code: " + columnHeader.getColumnName()
+ " - Invalid Type " + colType.getJdbcName(dialect) + " (neither varchar nor int)");
}
}
if (colType.isDateType()) {
return JsonParserHelper.convertFrom(columnValue, columnHeader.getColumnName(), dateFormat, locale);
}
if (colType.isDateTimeType()) {
return JsonParserHelper.convertDateTimeFrom(columnValue, columnHeader.getColumnName(), dateFormat, locale);
}
if (colType.isAnyIntegerType()) {
return helper.convertToInteger(columnValue, columnHeader.getColumnName(), locale);
}
if (colType.isDecimalType()) {
return helper.convertFrom(columnValue, columnHeader.getColumnName(), locale);
}
if (colType.isBooleanType()) {
final Boolean boolValue = BooleanUtils.toBooleanObject(columnValue);
if (boolValue == null) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.boolean.format",
"The parameter " + columnHeader.getColumnName() + " has value: " + columnValue + " which is invalid boolean value.",
columnHeader.getColumnName(), columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
return boolValue;
}
if (colType.isStringType()) {
if (columnHeader.getColumnLength() > 0 && columnValue.length() > columnHeader.getColumnLength()) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.datatable.entry.column.exceeds.maxlength",
"The column `" + columnHeader.getColumnName() + "` exceeds its defined max-length ", columnHeader.getColumnName(),
columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
}
return columnValue;
}
public static String camelToSnake(final String camelStr) {
return camelStr == null ? null
: camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
}
}
@@ -22,8 +22,10 @@ import static java.util.Collections.emptyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.when;
import com.google.gson.JsonObject;
@@ -31,6 +33,7 @@ import java.util.List;
import java.util.stream.Stream;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
@@ -71,6 +74,7 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test
public void testSqlInjectionCaughtQueryDataTable() {
mockDatatableValidation();
assertThrows(SQLInjectionException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "' or 1=1");
});
@@ -78,6 +82,7 @@ public class ReadWriteNonCoreDataServiceImplTest {
@Test
public void testSqlInjectionCaughtQueryDataTable2() {
mockDatatableValidation();
assertThrows(SQLInjectionException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "1; DROP TABLE m_loan; SELECT");
});
@@ -87,18 +92,24 @@ public class ReadWriteNonCoreDataServiceImplTest {
public void testQueryDataTableSuccess() {
mockDatatableValidation();
SqlRowSet sqlRS = Mockito.mock(SqlRowSet.class);
when(jdbcTemplate.queryForRowSet(eq("select rc1,rc2 from table where cf1 = ?"), any(Object.class))).thenReturn(sqlRS);
when(jdbcTemplate.queryForRowSet(eq("SELECT \"rc1\", \"rc2\" FROM \"table\" WHERE \"cf1\" = ?"), any(Object.class)))
.thenReturn(sqlRS);
when(sqlRS.next()).thenReturn(true).thenReturn(false);
when(sqlRS.getObject(anyString())).thenReturn("value1").thenReturn("value2");
when(sqlGenerator.escape(anyString())).thenReturn("rc1").thenReturn("rc2").thenReturn("table").thenReturn("cf1");
when(sqlGenerator.buildSelect(anyCollection(), nullable(String.class), eq(false))).thenReturn("SELECT \"rc1\", \"rc2\"");
when(sqlGenerator.buildFrom(anyString(), nullable(String.class), eq(false))).thenReturn("FROM \"table\"");
when(sqlGenerator.escape(anyString())).thenReturn("\"cf1\"");
when(sqlGenerator.alias(anyString(), nullable(String.class))).thenReturn("\"cf1\"");
when(databaseTypeResolver.isPostgreSQL()).thenReturn(true);
when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.POSTGRESQL);
DatabaseType dialect = databaseTypeResolver.databaseType();
ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", "text", 10L, false, false, emptyList(), null, false,
false);
false, dialect);
ResultsetColumnHeaderData rc1 = ResultsetColumnHeaderData.detailed("rc1", "text", 10L, false, false, emptyList(), null, false,
false);
false, dialect);
ResultsetColumnHeaderData rc2 = ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, emptyList(), null, false,
false);
false, dialect);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1, rc1, rc2));
List<JsonObject> results = underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2");
@@ -125,12 +136,14 @@ public class ReadWriteNonCoreDataServiceImplTest {
@MethodSource("provideParameters")
public void testQueryDataTableInvalidParameterError(String columnType, String errorMessage) {
mockDatatableValidation();
when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.POSTGRESQL);
DatabaseType dialect = databaseTypeResolver.databaseType();
ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", columnType, 10L, false, false, emptyList(), null, false,
false);
false, dialect);
ResultsetColumnHeaderData rc1 = ResultsetColumnHeaderData.detailed("rc1", "text", 10L, false, false, emptyList(), null, false,
false);
false, dialect);
ResultsetColumnHeaderData rc2 = ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, emptyList(), null, false,
false);
false, dialect);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1, rc1, rc2));
PlatformApiDataValidationException thrown = assertThrows(PlatformApiDataValidationException.class,
@@ -34,6 +34,7 @@ import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
@@ -46,7 +47,6 @@ import org.apache.fineract.interoperation.domain.InteropActionState;
import org.apache.fineract.interoperation.domain.InteropIdentifierType;
import org.apache.fineract.interoperation.domain.InteropTransactionRole;
import org.apache.fineract.interoperation.util.InteropUtil;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.junit.jupiter.api.Assertions;