MIFOSX-365: Simplify 'Data Tables' functionality
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||||
|
VALUES
|
||||||
|
('configuration', 'CREATE_DATATABLE', 'DATATABLE', 'CREATE', 0),
|
||||||
|
('configuration', 'CREATE_DATATABLE_CHECKER', 'DATATABLE', 'CREATE', 0),
|
||||||
|
('configuration', 'UPDATE_DATATABLE', 'DATATABLE', 'UPDATE', 0),
|
||||||
|
('configuration', 'UPDATE_DATATABLE_CHECKER', 'DATATABLE', 'UPDATE', 0),
|
||||||
|
('configuration', 'DELETE_DATATABLE', 'DATATABLE', 'DELETE', 0),
|
||||||
|
('configuration', 'DELETE_DATATABLE_CHECKER', 'DATATABLE', 'DELETE', 0);
|
||||||
|
|
||||||
@@ -108,6 +108,21 @@ public class CommandWrapper {
|
|||||||
return this.actionName.equalsIgnoreCase("CREATE");
|
return this.actionName.equalsIgnoreCase("CREATE");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCreateDatatable() {
|
||||||
|
return (this.actionName.equalsIgnoreCase("CREATE") && this.href.startsWith("/datatables/")
|
||||||
|
&& this.entityId == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleteDatatable() {
|
||||||
|
return (this.actionName.equalsIgnoreCase("DELETE") && this.href.startsWith("/datatables/")
|
||||||
|
&& this.entityId == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdateDatatable() {
|
||||||
|
return (this.actionName.equalsIgnoreCase("UPDATE") && this.href.startsWith("/datatables/")
|
||||||
|
&& this.entityId == null);
|
||||||
|
}
|
||||||
|
|
||||||
public String getTaskPermissionName() {
|
public String getTaskPermissionName() {
|
||||||
return this.taskPermissionName;
|
return this.taskPermissionName;
|
||||||
}
|
}
|
||||||
|
|||||||
+27
@@ -356,6 +356,33 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createDBDatatable(final String json) {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "DATATABLE";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/datatables/";
|
||||||
|
this.json = json;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateDBDatatable(final String datatable, final String json) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "DATATABLE";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/datatables/" + datatable;
|
||||||
|
this.json = json;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder deleteDBDatatable(final String datatable, final String json) {
|
||||||
|
this.actionName = "DELETE";
|
||||||
|
this.entityName = "DATATABLE";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/datatables/" + datatable;
|
||||||
|
this.json = json;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createDatatable(final String datatable, final Long apptableId, final Long datatableId) {
|
public CommandWrapperBuilder createDatatable(final String datatable, final Long apptableId, final Long datatableId) {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
commonDatatableSettings(datatable, apptableId, datatableId);
|
commonDatatableSettings(datatable, apptableId, datatableId);
|
||||||
|
|||||||
+2
-2
@@ -63,7 +63,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
|||||||
final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(),
|
final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(),
|
||||||
wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(),
|
wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(),
|
||||||
wrapper.getSavingsId(), wrapper.getCodeId(), wrapper.getSupportedEntityType(), wrapper.getSupportedEntityId(),
|
wrapper.getSavingsId(), wrapper.getCodeId(), wrapper.getSupportedEntityType(), wrapper.getSupportedEntityId(),
|
||||||
wrapper.getTransactionId());
|
wrapper.getTransactionId(), wrapper.getHref());
|
||||||
|
|
||||||
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
|
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
|
||||||
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
|
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
|
||||||
@@ -86,7 +86,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
|||||||
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
|
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
|
||||||
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand,
|
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand,
|
||||||
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
|
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
|
||||||
commandSourceInput.subresourceId());
|
commandSourceInput.subresourceId(), commandSourceInput.getResourceGetUrl());
|
||||||
|
|
||||||
final boolean makerCheckerApproval = true;
|
final boolean makerCheckerApproval = true;
|
||||||
return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval);
|
return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval);
|
||||||
|
|||||||
+8
-2
@@ -78,7 +78,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
commandSourceResult.updateJsonTo(changesOnlyJson);
|
commandSourceResult.updateJsonTo(changesOnlyJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.hasChanges() && wrapper.isUpdateOperation()) {
|
if (!result.hasChanges() && wrapper.isUpdateOperation() && !wrapper.isUpdateDatatable()) {
|
||||||
commandSourceResult.updateJsonTo(null);
|
commandSourceResult.updateJsonTo(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,13 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
if (wrapper.isConfigurationResource()) {
|
if (wrapper.isConfigurationResource()) {
|
||||||
handler = applicationContext.getBean("updateGlobalConfigurationCommandHandler", NewCommandSourceHandler.class);
|
handler = applicationContext.getBean("updateGlobalConfigurationCommandHandler", NewCommandSourceHandler.class);
|
||||||
} else if (wrapper.isDatatableResource()) {
|
} else if (wrapper.isDatatableResource()) {
|
||||||
if (wrapper.isCreate()) {
|
if (wrapper.isCreateDatatable()) {
|
||||||
|
handler = applicationContext.getBean("createDatatableCommandHandler", NewCommandSourceHandler.class);
|
||||||
|
} else if (wrapper.isDeleteDatatable()) {
|
||||||
|
handler = applicationContext.getBean("deleteDatatableCommandHandler", NewCommandSourceHandler.class);
|
||||||
|
} else if (wrapper.isUpdateDatatable()) {
|
||||||
|
handler = applicationContext.getBean("updateDatatableCommandHandler", NewCommandSourceHandler.class);
|
||||||
|
} else if (wrapper.isCreate()) {
|
||||||
handler = applicationContext.getBean("createDatatableEntryCommandHandler", NewCommandSourceHandler.class);
|
handler = applicationContext.getBean("createDatatableEntryCommandHandler", NewCommandSourceHandler.class);
|
||||||
} else if (wrapper.isUpdateMultiple()) {
|
} else if (wrapper.isUpdateMultiple()) {
|
||||||
handler = applicationContext.getBean("updateOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
|
handler = applicationContext.getBean("updateOneToManyDatatableEntryCommandHandler", NewCommandSourceHandler.class);
|
||||||
|
|||||||
+13
-5
@@ -45,24 +45,27 @@ public final class JsonCommand {
|
|||||||
private final String supportedEntityType;
|
private final String supportedEntityType;
|
||||||
private final Long supportedEntityId;
|
private final Long supportedEntityId;
|
||||||
private final String transactionId;
|
private final String transactionId;
|
||||||
|
private final String url;
|
||||||
public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper,
|
public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper,
|
||||||
final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId,
|
final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId,
|
||||||
final Long loanId, final Long savingsId, final Long codeId, final String supportedEntityType, final Long supportedEntityId,
|
final Long loanId, final Long savingsId, final Long codeId, final String supportedEntityType, final Long supportedEntityId,
|
||||||
final String transactionId) {
|
final String transactionId, final String url) {
|
||||||
return new JsonCommand(null, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
|
return new JsonCommand(null, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
|
||||||
clientId, loanId, savingsId, codeId, supportedEntityType, supportedEntityId, transactionId);
|
clientId, loanId, savingsId, codeId, supportedEntityType, supportedEntityId, transactionId, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
|
public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
|
||||||
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId) {
|
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
|
||||||
|
final String url) {
|
||||||
return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, null, null,
|
return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, null, null,
|
||||||
null, null, null, null, null, null);
|
null, null, null, null, null, null, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
|
public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
|
||||||
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
|
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
|
||||||
final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final Long codeId,
|
final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final Long codeId,
|
||||||
final String supportedEntityType, final Long supportedEntityId, final String transactionId) {
|
final String supportedEntityType, final Long supportedEntityId, final String transactionId,
|
||||||
|
final String url) {
|
||||||
this.commandId = commandId;
|
this.commandId = commandId;
|
||||||
this.jsonCommand = jsonCommand;
|
this.jsonCommand = jsonCommand;
|
||||||
this.parsedCommand = parsedCommand;
|
this.parsedCommand = parsedCommand;
|
||||||
@@ -78,6 +81,7 @@ public final class JsonCommand {
|
|||||||
this.supportedEntityType = supportedEntityType;
|
this.supportedEntityType = supportedEntityType;
|
||||||
this.supportedEntityId = supportedEntityId;
|
this.supportedEntityId = supportedEntityId;
|
||||||
this.transactionId = transactionId;
|
this.transactionId = transactionId;
|
||||||
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String json() {
|
public String json() {
|
||||||
@@ -144,6 +148,10 @@ public final class JsonCommand {
|
|||||||
public String getTransactionId() {
|
public String getTransactionId() {
|
||||||
return this.transactionId;
|
return this.transactionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return this.url;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) {
|
private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) {
|
||||||
boolean differenceExists = false;
|
boolean differenceExists = false;
|
||||||
|
|||||||
+40
@@ -221,6 +221,27 @@ public class DataValidatorBuilder {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataValidatorBuilder isNotOneOfTheseValues(final Object... values) {
|
||||||
|
if (value == null && ignoreNullValue) { return this; }
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
List<Object> valuesList = Arrays.asList(values);
|
||||||
|
|
||||||
|
if (valuesList.contains(this.value)) {
|
||||||
|
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||||
|
.append(".is.one.of.expected.enumerations");
|
||||||
|
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must not be one of [ ")
|
||||||
|
.append(values).append(" ] ").append(".");
|
||||||
|
|
||||||
|
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
|
||||||
|
defaultEnglishMessage.toString(), parameter, this.value, values);
|
||||||
|
|
||||||
|
dataValidationErrors.add(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DataValidatorBuilder positiveAmount() {
|
public DataValidatorBuilder positiveAmount() {
|
||||||
if (value == null && ignoreNullValue) { return this; }
|
if (value == null && ignoreNullValue) { return this; }
|
||||||
@@ -533,4 +554,23 @@ public class DataValidatorBuilder {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataValidatorBuilder matchesRegularExpression(final String expression) {
|
||||||
|
if (value == null && ignoreNullValue) { return this; }
|
||||||
|
|
||||||
|
if (!value.toString().matches(expression)) {
|
||||||
|
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||||
|
.append(".does.not.match.regexp");
|
||||||
|
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter)
|
||||||
|
.append(" must match the provided regular expression ")
|
||||||
|
.append(expression).append(" ] ").append(".");
|
||||||
|
|
||||||
|
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
|
||||||
|
defaultEnglishMessage.toString(), parameter, this.value, expression);
|
||||||
|
|
||||||
|
dataValidationErrors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
package org.mifosplatform.infrastructure.core.serialization;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
||||||
|
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
|
||||||
|
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DatatableCommandFromApiJsonDeserializer {
|
||||||
|
|
||||||
|
private final static String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
|
||||||
|
/**
|
||||||
|
* The parameters supported for this command.
|
||||||
|
*/
|
||||||
|
private final Set<String> supportedParametersForCreate = new HashSet<String>(Arrays.asList(
|
||||||
|
"datatableName", "apptableName", "multiRow", "columns"));
|
||||||
|
private final Set<String> supportedParametersForCreateColumns = new HashSet<String>(Arrays.asList(
|
||||||
|
"name", "type", "length", "mandatory"));
|
||||||
|
private final Set<String> supportedParametersForUpdate = new HashSet<String>(Arrays.asList(
|
||||||
|
"apptableName", "changeColumns", "addColumns", "dropColumns"));
|
||||||
|
private final Set<String> supportedParametersForAddColumns = new HashSet<String>(Arrays.asList(
|
||||||
|
"name", "type", "length", "mandatory", "after"));
|
||||||
|
private final Set<String> supportedParametersForChangeColumns = new HashSet<String>(Arrays.asList(
|
||||||
|
"name", "newName", "type", "length", "mandatory", "after"));
|
||||||
|
private final Set<String> supportedParametersForDropColumns = new HashSet<String>(Arrays.asList(
|
||||||
|
"name"));
|
||||||
|
private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date" };
|
||||||
|
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public DatatableCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||||
|
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForCreate(final String json) {
|
||||||
|
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||||
|
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForCreate);
|
||||||
|
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
|
||||||
|
|
||||||
|
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||||
|
final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element);
|
||||||
|
final String fkColumnName = apptableName.substring(2) + "_id";
|
||||||
|
baseDataValidator.reset().parameter("apptableName").value(apptableName).notNull().notBlank().notExceedingLengthOf(50);
|
||||||
|
|
||||||
|
final String datatableName = fromApiJsonHelper.extractStringNamed("datatableName", element);
|
||||||
|
baseDataValidator.reset().parameter("datatableName").value(datatableName).notNull().notBlank().notExceedingLengthOf(50)
|
||||||
|
.matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||||
|
|
||||||
|
final Boolean multiRow = fromApiJsonHelper.extractBooleanNamed("multiRow", element);
|
||||||
|
baseDataValidator.reset().parameter("multiRow").value(multiRow).ignoreIfNull().trueOrFalseRequired(multiRow);
|
||||||
|
|
||||||
|
final JsonArray columns = fromApiJsonHelper.extractJsonArrayNamed("columns", element);
|
||||||
|
baseDataValidator.reset().parameter("columns").value(columns).notNull();
|
||||||
|
|
||||||
|
for (JsonElement column : columns) {
|
||||||
|
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForCreateColumns);
|
||||||
|
|
||||||
|
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||||
|
baseDataValidator.reset().parameter("name").value(name).notNull().notBlank().notExceedingLengthOf(50)
|
||||||
|
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||||
|
|
||||||
|
final String type = fromApiJsonHelper.extractStringNamed("type", column);
|
||||||
|
baseDataValidator.reset().parameter("type").value(type).notNull().notBlank().isOneOfTheseValues(supportedColumnTypes);
|
||||||
|
|
||||||
|
final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column);
|
||||||
|
if (type.equals("String")) {
|
||||||
|
baseDataValidator.reset().parameter("length").value(length).notNull().notBlank()
|
||||||
|
.zeroOrPositiveAmount();
|
||||||
|
} else {
|
||||||
|
baseDataValidator.reset().parameter("length").value(length).ignoreIfNull().notBlank()
|
||||||
|
.zeroOrPositiveAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
|
||||||
|
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().trueOrFalseRequired(mandatory);
|
||||||
|
}
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForUpdate(final String json) {
|
||||||
|
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||||
|
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParametersForUpdate);
|
||||||
|
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable");
|
||||||
|
|
||||||
|
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||||
|
final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element);
|
||||||
|
final String fkColumnName = apptableName.substring(2) + "_id";
|
||||||
|
baseDataValidator.reset().parameter("apptableName").value(apptableName).notBlank().notExceedingLengthOf(50);
|
||||||
|
|
||||||
|
if (fromApiJsonHelper.parameterExists("changeColumns", element)) {
|
||||||
|
final JsonArray changeColumns = fromApiJsonHelper.extractJsonArrayNamed("changeColumns", element);
|
||||||
|
baseDataValidator.reset().parameter("changeColumns").value(changeColumns).ignoreIfNull();
|
||||||
|
|
||||||
|
for (JsonElement column : changeColumns) {
|
||||||
|
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForChangeColumns);
|
||||||
|
|
||||||
|
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||||
|
baseDataValidator.reset().parameter("name").value(name).notNull().notBlank().notExceedingLengthOf(50)
|
||||||
|
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||||
|
|
||||||
|
final String newName = fromApiJsonHelper.extractStringNamed("newName", column);
|
||||||
|
baseDataValidator.reset().parameter("newName").value(newName).ignoreIfNull().notBlank()
|
||||||
|
.notExceedingLengthOf(50).isNotOneOfTheseValues("id", fkColumnName)
|
||||||
|
.matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||||
|
|
||||||
|
final String type = fromApiJsonHelper.extractStringNamed("type", column);
|
||||||
|
baseDataValidator.reset().parameter("type").value(type).notNull().notBlank()
|
||||||
|
.isOneOfTheseValues(supportedColumnTypes);
|
||||||
|
|
||||||
|
final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column);
|
||||||
|
if (type.equals("String")) {
|
||||||
|
baseDataValidator.reset().parameter("length").value(length).notNull().notBlank()
|
||||||
|
.zeroOrPositiveAmount();
|
||||||
|
} else {
|
||||||
|
baseDataValidator.reset().parameter("length").value(length).ignoreIfNull().notBlank()
|
||||||
|
.zeroOrPositiveAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
|
||||||
|
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().trueOrFalseRequired(mandatory);
|
||||||
|
|
||||||
|
final Boolean after = fromApiJsonHelper.extractBooleanNamed("after", column);
|
||||||
|
baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fromApiJsonHelper.parameterExists("addColumns", element)) {
|
||||||
|
final JsonArray addColumns = fromApiJsonHelper.extractJsonArrayNamed("addColumns", element);
|
||||||
|
baseDataValidator.reset().parameter("addColumns").value(addColumns).ignoreIfNull();
|
||||||
|
|
||||||
|
for (JsonElement column : addColumns) {
|
||||||
|
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForAddColumns);
|
||||||
|
|
||||||
|
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||||
|
baseDataValidator.reset().parameter("name").value(name).notNull().notBlank().notExceedingLengthOf(50)
|
||||||
|
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||||
|
|
||||||
|
final String type = fromApiJsonHelper.extractStringNamed("type", column);
|
||||||
|
baseDataValidator.reset().parameter("type").value(type).notNull().notBlank()
|
||||||
|
.isOneOfTheseValues(supportedColumnTypes);
|
||||||
|
|
||||||
|
final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column);
|
||||||
|
if (type.equals("String")) {
|
||||||
|
baseDataValidator.reset().parameter("length").value(length).notNull().notBlank()
|
||||||
|
.zeroOrPositiveAmount();
|
||||||
|
} else {
|
||||||
|
baseDataValidator.reset().parameter("length").value(length).ignoreIfNull().notBlank()
|
||||||
|
.zeroOrPositiveAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column);
|
||||||
|
baseDataValidator.reset().parameter("mandatory").value(mandatory).ignoreIfNull().trueOrFalseRequired(mandatory);
|
||||||
|
|
||||||
|
final Boolean after = fromApiJsonHelper.extractBooleanNamed("after", column);
|
||||||
|
baseDataValidator.reset().parameter("after").value(after).ignoreIfNull().notBlank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fromApiJsonHelper.parameterExists("dropColumns", element)) {
|
||||||
|
final JsonArray dropColumns = fromApiJsonHelper.extractJsonArrayNamed("dropColumns", element);
|
||||||
|
baseDataValidator.reset().parameter("dropColumns").value(dropColumns).ignoreIfNull();
|
||||||
|
|
||||||
|
for (JsonElement column : dropColumns) {
|
||||||
|
fromApiJsonHelper.checkForUnsupportedParameters(column.getAsJsonObject(), supportedParametersForDropColumns);
|
||||||
|
|
||||||
|
final String name = fromApiJsonHelper.extractStringNamed("name", column);
|
||||||
|
baseDataValidator.reset().parameter("name").value(name).notNull().notBlank().notExceedingLengthOf(50)
|
||||||
|
.isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||||
|
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||||
|
"Validation errors exist.", dataValidationErrors); }
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
@@ -7,11 +7,13 @@ package org.mifosplatform.infrastructure.core.serialization;
|
|||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@@ -78,6 +80,21 @@ public class FromJsonHelper {
|
|||||||
|
|
||||||
if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); }
|
if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkForUnsupportedParameters(final JsonObject object, final Set<String> supportedParams) {
|
||||||
|
if (object == null) { throw new InvalidParameterException(); }
|
||||||
|
|
||||||
|
Set<Entry<String, JsonElement>> entries = object.entrySet();
|
||||||
|
List<String> unsupportedParameterList = new ArrayList<String>();
|
||||||
|
|
||||||
|
for (Entry<String, JsonElement> providedParameter : entries) {
|
||||||
|
if (!supportedParams.contains(providedParameter.getKey())) {
|
||||||
|
unsupportedParameterList.add(providedParameter.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); }
|
||||||
|
}
|
||||||
|
|
||||||
public JsonElement parse(final String json) {
|
public JsonElement parse(final String json) {
|
||||||
return parser.parse(json);
|
return parser.parse(json);
|
||||||
|
|||||||
+52
@@ -70,6 +70,46 @@ public class DatatablesApiResource {
|
|||||||
return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
public String createDatatable(final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||||
|
.createDBDatatable(apiRequestBodyAsJson).build();
|
||||||
|
|
||||||
|
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("{datatableName}")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
public String updateDatatable(@PathParam("datatableName") final String datatableName,
|
||||||
|
final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||||
|
.updateDBDatatable(datatableName, apiRequestBodyAsJson).build();
|
||||||
|
|
||||||
|
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("{datatableName}")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
public String deleteDatatable(@PathParam("datatableName") final String datatableName,
|
||||||
|
final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||||
|
.deleteDBDatatable(datatableName, apiRequestBodyAsJson).build();
|
||||||
|
|
||||||
|
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("register/{datatable}/{apptable}")
|
@Path("register/{datatable}/{apptable}")
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@@ -95,6 +135,18 @@ public class DatatablesApiResource {
|
|||||||
return this.toApiJsonSerializer.serialize(result);
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{datatable}")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
public String getDatatable(@PathParam("datatable") final String datatable, @Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
|
final DatatableData result = this.readWriteNonCoreDataService.retrieveDatatable(datatable);
|
||||||
|
|
||||||
|
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
||||||
|
return this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{datatable}/{apptableId}")
|
@Path("{datatable}/{apptableId}")
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
|||||||
+11
-4
@@ -5,6 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.mifosplatform.infrastructure.dataqueries.data;
|
package org.mifosplatform.infrastructure.dataqueries.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable data object representing datatable data.
|
* Immutable data object representing datatable data.
|
||||||
*/
|
*/
|
||||||
@@ -14,13 +16,18 @@ public class DatatableData {
|
|||||||
private final String applicationTableName;
|
private final String applicationTableName;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final String registeredTableName;
|
private final String registeredTableName;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public static DatatableData create(final String applicationTableName, final String registeredTableName) {
|
private final List<ResultsetColumnHeaderData> columnHeaderData;
|
||||||
return new DatatableData(applicationTableName, registeredTableName);
|
|
||||||
|
public static DatatableData create(final String applicationTableName, final String registeredTableName,
|
||||||
|
final List<ResultsetColumnHeaderData> columnHeaderData) {
|
||||||
|
return new DatatableData(applicationTableName, registeredTableName, columnHeaderData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DatatableData(final String applicationTableName, final String registeredTableName) {
|
private DatatableData(final String applicationTableName, final String registeredTableName,
|
||||||
|
final List<ResultsetColumnHeaderData> columnHeaderData) {
|
||||||
this.applicationTableName = applicationTableName;
|
this.applicationTableName = applicationTableName;
|
||||||
this.registeredTableName = registeredTableName;
|
this.registeredTableName = registeredTableName;
|
||||||
|
this.columnHeaderData = columnHeaderData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
package org.mifosplatform.infrastructure.dataqueries.handler;
|
||||||
|
|
||||||
|
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.mifosplatform.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CreateDatatableCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final ReadWriteNonCoreDataService writePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CreateDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
|
||||||
|
this.writePlatformService = writePlatformService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
|
||||||
|
return this.writePlatformService.createDatatable(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
package org.mifosplatform.infrastructure.dataqueries.handler;
|
||||||
|
|
||||||
|
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
|
import org.mifosplatform.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class DeleteDatatableCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final ReadWriteNonCoreDataService writePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public DeleteDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
|
||||||
|
this.writePlatformService = writePlatformService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
|
||||||
|
final String datatableName = command.getUrl().replaceAll("/datatables/", "");
|
||||||
|
|
||||||
|
this.writePlatformService.deleteDatatable(datatableName);
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder()
|
||||||
|
.withCommandId(command.commandId())
|
||||||
|
.withResourceIdAsString(datatableName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
package org.mifosplatform.infrastructure.dataqueries.handler;
|
||||||
|
|
||||||
|
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
|
import org.mifosplatform.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UpdateDatatableCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final ReadWriteNonCoreDataService writePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public UpdateDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
|
||||||
|
this.writePlatformService = writePlatformService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
|
||||||
|
final String datatableName = command.getUrl().replaceAll("/datatables/", "");
|
||||||
|
|
||||||
|
this.writePlatformService.updateDatatable(datatableName, command);
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder()
|
||||||
|
.withCommandId(command.commandId())
|
||||||
|
.withResourceIdAsString(datatableName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-1
@@ -16,7 +16,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||||||
public interface ReadWriteNonCoreDataService {
|
public interface ReadWriteNonCoreDataService {
|
||||||
|
|
||||||
List<DatatableData> retrieveDatatableNames(String appTable);
|
List<DatatableData> retrieveDatatableNames(String appTable);
|
||||||
|
|
||||||
|
DatatableData retrieveDatatable(String datatable);
|
||||||
|
|
||||||
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
|
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
|
||||||
void registerDatatable(String datatable, String appTable);
|
void registerDatatable(String datatable, String appTable);
|
||||||
|
|
||||||
@@ -25,6 +27,12 @@ public interface ReadWriteNonCoreDataService {
|
|||||||
|
|
||||||
GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
|
GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
|
||||||
|
|
||||||
|
CommandProcessingResult createDatatable(JsonCommand command);
|
||||||
|
|
||||||
|
void updateDatatable(String datatableName, JsonCommand command);
|
||||||
|
|
||||||
|
void deleteDatatable(String datatableName);
|
||||||
|
|
||||||
CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, JsonCommand command);
|
CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult updateDatatableEntryOneToOne(String datatable, Long appTableId, JsonCommand command);
|
CommandProcessingResult updateDatatableEntryOneToOne(String datatable, Long appTableId, JsonCommand command);
|
||||||
|
|||||||
+291
-3
@@ -25,6 +25,7 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
|||||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
|
import org.mifosplatform.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer;
|
||||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper;
|
import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper;
|
||||||
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
|
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
|
||||||
@@ -47,12 +48,20 @@ import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataService {
|
public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataService {
|
||||||
|
|
||||||
|
private final static String DATATABLE_NAME_REGEX_PATTERN = "^[a-zA-Z][a-zA-Z0-9\\-_\\s]{0,48}[a-zA-Z0-9]$";
|
||||||
|
|
||||||
private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class);
|
private final static Logger logger = LoggerFactory.getLogger(ReadWriteNonCoreDataServiceImpl.class);
|
||||||
|
private final static HashMap<String, String> apiTypeToMySQL = new HashMap<String, String>() {{
|
||||||
|
put("String", "VARCHAR"); put("Number", "INT"); put("Decimal", "DECIMAL"); put("Date", "DATE");
|
||||||
|
}};
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
@@ -60,16 +69,19 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
|||||||
private final FromJsonHelper fromJsonHelper;
|
private final FromJsonHelper fromJsonHelper;
|
||||||
private final JsonParserHelper helper;
|
private final JsonParserHelper helper;
|
||||||
private final GenericDataService genericDataService;
|
private final GenericDataService genericDataService;
|
||||||
|
private final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ReadWriteNonCoreDataServiceImpl(final TenantAwareRoutingDataSource dataSource, final PlatformSecurityContext context,
|
public ReadWriteNonCoreDataServiceImpl(final TenantAwareRoutingDataSource dataSource, final PlatformSecurityContext context,
|
||||||
final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService) {
|
final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService,
|
||||||
|
final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer) {
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
|
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.fromJsonHelper = fromJsonHelper;
|
this.fromJsonHelper = fromJsonHelper;
|
||||||
this.helper = new JsonParserHelper();
|
this.helper = new JsonParserHelper();
|
||||||
this.genericDataService = genericDataService;
|
this.genericDataService = genericDataService;
|
||||||
|
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -96,12 +108,41 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
|||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
final String appTableName = rs.getString("application_table_name");
|
final String appTableName = rs.getString("application_table_name");
|
||||||
final String registeredDatatableName = rs.getString("registered_table_name");
|
final String registeredDatatableName = rs.getString("registered_table_name");
|
||||||
|
final List<ResultsetColumnHeaderData> columnHeaderData =
|
||||||
datatables.add(DatatableData.create(appTableName, registeredDatatableName));
|
this.genericDataService.fillResultsetColumnHeaders(registeredDatatableName);
|
||||||
|
|
||||||
|
datatables.add(DatatableData.create(appTableName, registeredDatatableName, columnHeaderData));
|
||||||
}
|
}
|
||||||
|
|
||||||
return datatables;
|
return datatables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatatableData retrieveDatatable(final String datatable) {
|
||||||
|
|
||||||
|
// PERMITTED datatables
|
||||||
|
final String sql = "select application_table_name, registered_table_name" + " from x_registered_table " + " where exists"
|
||||||
|
+ " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
|
||||||
|
+ " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
|
||||||
|
+ " where ur.appuser_id = " + context.authenticatedUser().getId()
|
||||||
|
+ " and registered_table_name='" + datatable + "'"
|
||||||
|
+ " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
|
||||||
|
+ " order by application_table_name, registered_table_name";
|
||||||
|
|
||||||
|
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
|
||||||
|
|
||||||
|
DatatableData datatableData = null;
|
||||||
|
while (rs.next()) {
|
||||||
|
final String appTableName = rs.getString("application_table_name");
|
||||||
|
final String registeredDatatableName = rs.getString("registered_table_name");
|
||||||
|
final List<ResultsetColumnHeaderData> columnHeaderData =
|
||||||
|
this.genericDataService.fillResultsetColumnHeaders(registeredDatatableName);
|
||||||
|
|
||||||
|
datatableData = DatatableData.create(appTableName, registeredDatatableName, columnHeaderData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return datatableData;
|
||||||
|
}
|
||||||
|
|
||||||
private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
|
private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
|
||||||
logger.error(dve.getMessage(), dve);
|
logger.error(dve.getMessage(), dve);
|
||||||
@@ -240,6 +281,253 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean datatableExists(final String datatable) {
|
||||||
|
|
||||||
|
String sql = "SELECT COUNT(*) FROM `x_registered_table` WHERE `registered_table_name`='" + datatable + "'";
|
||||||
|
Integer rowCount = this.jdbcTemplate.queryForInt(sql);
|
||||||
|
|
||||||
|
return (rowCount > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateDatatableExists(final String name) {
|
||||||
|
|
||||||
|
if (!datatableExists(name)) {
|
||||||
|
throw new PlatformDataIntegrityException("error.msg.datatables.missing.table.name",
|
||||||
|
"Table '" + name + "' does not exist.", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateDatatableNotExists(final String name) {
|
||||||
|
|
||||||
|
if (datatableExists(name)) {
|
||||||
|
throw new PlatformDataIntegrityException("error.msg.datatables.duplicate.table.name",
|
||||||
|
"Table '" + name + "' already exists.", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateDatatableName(final String name) {
|
||||||
|
|
||||||
|
if (name == null || name.isEmpty()) {
|
||||||
|
throw new PlatformDataIntegrityException("error.msg.datatables.datatable.null.name",
|
||||||
|
"Data table name must not be blank.");
|
||||||
|
} else if (!name.matches(DATATABLE_NAME_REGEX_PATTERN)) {
|
||||||
|
throw new PlatformDataIntegrityException("error.msg.datatables.datatable.invalid.name.regex",
|
||||||
|
"Invalid data table name.", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder) {
|
||||||
|
|
||||||
|
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||||
|
String type = (column.has("type")) ? column.get("type").getAsString() : null;
|
||||||
|
Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
|
||||||
|
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
|
||||||
|
|
||||||
|
String mysqlType = apiTypeToMySQL.get(type);
|
||||||
|
sqlBuilder = sqlBuilder.append("`" + name + "` " + mysqlType);
|
||||||
|
|
||||||
|
if (type != null)
|
||||||
|
{
|
||||||
|
if (type.equals("String")) {
|
||||||
|
sqlBuilder = sqlBuilder.append("(" + length + ")");
|
||||||
|
} else if (type.equals("Decimal")) {
|
||||||
|
sqlBuilder = sqlBuilder.append("(19,6)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mandatory != null) {
|
||||||
|
if (mandatory) {
|
||||||
|
sqlBuilder = sqlBuilder.append(" NOT NULL");
|
||||||
|
} else {
|
||||||
|
sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlBuilder = sqlBuilder.append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult createDatatable(final JsonCommand command) {
|
||||||
|
|
||||||
|
context.authenticatedUser();
|
||||||
|
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||||
|
|
||||||
|
JsonElement element = this.fromJsonHelper.parse(command.json());
|
||||||
|
JsonArray columns = this.fromJsonHelper.extractJsonArrayNamed("columns", element);
|
||||||
|
String datatableName = this.fromJsonHelper.extractStringNamed("datatableName", element);
|
||||||
|
String apptable = this.fromJsonHelper.extractStringNamed("apptableName", element);
|
||||||
|
Boolean multiRow = this.fromJsonHelper.extractBooleanNamed("multiRow", element);
|
||||||
|
|
||||||
|
if (multiRow == null) {
|
||||||
|
multiRow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDatatableName(datatableName);
|
||||||
|
validateDatatableName(apptable);
|
||||||
|
validateDatatableNotExists(datatableName);
|
||||||
|
|
||||||
|
String fkColumnName = apptable.substring(2) + "_id";
|
||||||
|
String fkName = datatableName.toLowerCase().replaceAll("\\s", "_");
|
||||||
|
StringBuilder sqlBuilder = new StringBuilder();
|
||||||
|
sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` (");
|
||||||
|
|
||||||
|
if (multiRow) {
|
||||||
|
sqlBuilder = sqlBuilder
|
||||||
|
.append("`id` BIGINT(20) NOT NULL AUTO_INCREMENT, ")
|
||||||
|
.append("`" + fkColumnName + "` BIGINT(20) NOT NULL, ");
|
||||||
|
} else {
|
||||||
|
sqlBuilder = sqlBuilder
|
||||||
|
.append("`" + fkColumnName + "` BIGINT(20) NOT NULL, ");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (JsonElement column : columns) {
|
||||||
|
parseDatatableColumnObjectForCreate(column.getAsJsonObject(), sqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing comma and space
|
||||||
|
sqlBuilder = sqlBuilder.delete(sqlBuilder.length() - 2, sqlBuilder.length());
|
||||||
|
|
||||||
|
if (multiRow) {
|
||||||
|
sqlBuilder = sqlBuilder
|
||||||
|
.append(", PRIMARY KEY (`id`)")
|
||||||
|
.append(", KEY `fk_" + apptable + "` (`" + fkColumnName + "`)")
|
||||||
|
.append(", CONSTRAINT `fk_" + fkName + "` ")
|
||||||
|
.append("FOREIGN KEY (`" + fkColumnName + "`) ")
|
||||||
|
.append("REFERENCES `" + apptable + "` (`id`)");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sqlBuilder = sqlBuilder
|
||||||
|
.append(", PRIMARY KEY (`" + fkColumnName + "`)")
|
||||||
|
.append(", CONSTRAINT `fk_" + fkName + "` ")
|
||||||
|
.append("FOREIGN KEY (`" + fkColumnName + "`) ")
|
||||||
|
.append("REFERENCES `" + apptable + "` (`id`)");
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlBuilder = sqlBuilder.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
|
||||||
|
this.jdbcTemplate.execute(sqlBuilder.toString());
|
||||||
|
|
||||||
|
registerDatatable(datatableName, apptable);
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder()
|
||||||
|
.withCommandId(command.commandId())
|
||||||
|
.withResourceIdAsString(datatableName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseDatatableColumnForUpdate(final JsonObject column, StringBuilder sqlBuilder) {
|
||||||
|
|
||||||
|
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||||
|
String newName = (column.has("newName")) ? column.get("newName").getAsString() : name;
|
||||||
|
String type = (column.has("type")) ? column.get("type").getAsString() : null;
|
||||||
|
Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
|
||||||
|
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
|
||||||
|
String after = (column.has("after")) ? column.get("after").getAsString() : null;
|
||||||
|
|
||||||
|
String mysqlType = apiTypeToMySQL.get(type);
|
||||||
|
sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + mysqlType);
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
if (type.equals("String") && length != null) {
|
||||||
|
sqlBuilder = sqlBuilder.append("(" + length + ")");
|
||||||
|
} else if (type.equals("Decimal")) {
|
||||||
|
sqlBuilder = sqlBuilder.append("(19,6)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mandatory != null) {
|
||||||
|
if (mandatory) {
|
||||||
|
sqlBuilder = sqlBuilder.append(" NOT NULL");
|
||||||
|
} else {
|
||||||
|
sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (after != null) {
|
||||||
|
sqlBuilder = sqlBuilder.append(" AFTER `" + after + "`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseDatatableColumnForAdd(final JsonObject column, StringBuilder sqlBuilder) {
|
||||||
|
|
||||||
|
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||||
|
String type = (column.has("type")) ? column.get("type").getAsString() : null;
|
||||||
|
Integer length = (column.has("length")) ? column.get("length").getAsInt() : null;
|
||||||
|
Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false;
|
||||||
|
String after = (column.has("after")) ? column.get("after").getAsString() : null;
|
||||||
|
|
||||||
|
String mysqlType = apiTypeToMySQL.get(type);
|
||||||
|
sqlBuilder = sqlBuilder.append(", ADD `" + name + "` " + mysqlType);
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
if (type.equals("String") && length != null) {
|
||||||
|
sqlBuilder = sqlBuilder.append("(" + length + ")");
|
||||||
|
} else if (type.equals("Decimal")) {
|
||||||
|
sqlBuilder = sqlBuilder.append("(19,6)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mandatory != null) {
|
||||||
|
if (mandatory) {
|
||||||
|
sqlBuilder = sqlBuilder.append(" NOT NULL");
|
||||||
|
} else {
|
||||||
|
sqlBuilder = sqlBuilder.append(" DEFAULT NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (after != null) {
|
||||||
|
sqlBuilder = sqlBuilder.append(" AFTER `" + after + "`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseDatatableColumnForDrop(final JsonObject column, StringBuilder sqlBuilder) {
|
||||||
|
|
||||||
|
String name = (column.has("name")) ? column.get("name").getAsString() : null;
|
||||||
|
|
||||||
|
sqlBuilder = sqlBuilder.append(", DROP COLUMN `" + name + "`");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void updateDatatable(final String datatableName, final JsonCommand command) {
|
||||||
|
|
||||||
|
context.authenticatedUser();
|
||||||
|
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
||||||
|
|
||||||
|
JsonElement element = this.fromJsonHelper.parse(command.json());
|
||||||
|
JsonArray changeColumns = this.fromJsonHelper.extractJsonArrayNamed("changeColumns", element);
|
||||||
|
JsonArray addColumns = this.fromJsonHelper.extractJsonArrayNamed("addColumns", element);
|
||||||
|
JsonArray dropColumns = this.fromJsonHelper.extractJsonArrayNamed("dropColumns", element);
|
||||||
|
|
||||||
|
validateDatatableName(datatableName);
|
||||||
|
validateDatatableExists(datatableName);
|
||||||
|
|
||||||
|
StringBuilder sqlBuilder = new StringBuilder("ALTER TABLE `" + datatableName + "`");
|
||||||
|
|
||||||
|
for (JsonElement column : changeColumns) {
|
||||||
|
parseDatatableColumnForUpdate(column.getAsJsonObject(), sqlBuilder);
|
||||||
|
}
|
||||||
|
for (JsonElement column : addColumns) {
|
||||||
|
parseDatatableColumnForAdd(column.getAsJsonObject(), sqlBuilder);
|
||||||
|
}
|
||||||
|
for (JsonElement column : dropColumns) {
|
||||||
|
parseDatatableColumnForDrop(column.getAsJsonObject(), sqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the first comma, right after ALTER TABLE `datatable`
|
||||||
|
sqlBuilder = sqlBuilder.deleteCharAt(sqlBuilder.indexOf(","));
|
||||||
|
this.jdbcTemplate.execute(sqlBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void deleteDatatable(final String datatableName) {
|
||||||
|
|
||||||
|
context.authenticatedUser();
|
||||||
|
|
||||||
|
validateDatatableName(datatableName);
|
||||||
|
validateDatatableExists(datatableName);
|
||||||
|
deregisterDatatable(datatableName);
|
||||||
|
|
||||||
|
String sql = "DROP TABLE `" + datatableName + "`";
|
||||||
|
this.jdbcTemplate.execute(sql);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult updateDatatableEntryOneToOne(final String dataTableName, final Long appTableId, final JsonCommand command) {
|
public CommandProcessingResult updateDatatableEntryOneToOne(final String dataTableName, final Long appTableId, final JsonCommand command) {
|
||||||
|
|||||||
Reference in New Issue
Block a user