diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/DataTableApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/DataTableApiResource.java index 8ff67b705..fec20566e 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/DataTableApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/DataTableApiResource.java @@ -8,6 +8,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -124,6 +125,29 @@ public class DataTableApiResource { return Response.ok().entity(entityIdentifier).build(); } + + + @PUT + @Path("{datatable}/{appTableId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public Response updateDatatableEntryOnetoOne( + @PathParam("datatable") final String datatable, + @PathParam("appTableId") final Long appTableId, + final String jsonRequestBody) { + + checkUserPermissionForDatatable(datatable, "UPDATE"); + Map queryParams = getQueryParamsFromJsonRequestBody(jsonRequestBody); + + this.readWriteNonCoreDataService.updateDatatableEntryOnetoOne(datatable, + appTableId, queryParams); + + EntityIdentifier entityIdentifier = new EntityIdentifier( + Long.valueOf(appTableId)); + + return Response.ok().entity(entityIdentifier).build(); + + } @DELETE @Path("{datatable}/{appTableId}") @@ -180,7 +204,7 @@ public class DataTableApiResource { for (int i = 0; i < jsonArr.length(); i++) { pName = (String) jsonArr.get(i); pValue = jsonObj.getString(pName); - logger.info(pName + " - " + pValue); + logger.info("getQueryParamsFromJsonRequestBody: " + pName + " - " + pValue); queryParams.put(pName, pValue); } return queryParams; diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataService.java b/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataService.java index 1d64bab8a..4967fafad 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataService.java @@ -27,6 +27,9 @@ public interface ReadWriteNonCoreDataService { void newDatatableEntry(String datatable, Long appTableId, Map queryParams); + + void updateDatatableEntryOnetoOne(String datatable, Long appTableId, + Map queryParams); void deleteDatatableEntries(String datatable, Long appTableId); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataServiceImpl.java index c42cd18a0..b7196f84d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/noncore/ReadWriteNonCoreDataServiceImpl.java @@ -12,6 +12,7 @@ import javax.sql.rowset.CachedRowSet; import org.apache.commons.lang.StringUtils; import org.mifosng.platform.api.data.AdditionalFieldsSetData; +import org.mifosng.platform.api.data.ApiParameterError; import org.mifosng.platform.api.data.DatatableData; import org.mifosng.platform.api.data.GenericResultsetData; import org.mifosng.platform.api.data.ResultsetColumnHeader; @@ -19,6 +20,7 @@ import org.mifosng.platform.api.data.ResultsetColumnValue; import org.mifosng.platform.api.data.ResultsetDataRow; import org.mifosng.platform.exceptions.AdditionalFieldsNotFoundException; import org.mifosng.platform.exceptions.DataTableNotFoundException; +import org.mifosng.platform.exceptions.PlatformApiDataValidationException; import org.mifosng.platform.exceptions.PlatformDataIntegrityException; import org.mifosng.platform.security.PlatformSecurityContext; import org.mifosng.platform.user.domain.AppUser; @@ -427,19 +429,24 @@ public class ReadWriteNonCoreDataServiceImpl implements + " not found"); } - private void checkMainResourceExistsWithinScope(String tableName, + private String getFullDatasetName(final String type, final String set) { + return type + "_x" + set; + } + + // only exclusively datatable functions below here + private void checkMainResourceExistsWithinScope(String appTable, Long appTableId) { - String unscopedSql = "select t.id from " + tableName + String unscopedSql = "select t.id from " + appTable + " t ${dataScopeCriteria} where t.id = " + appTableId; - String sql = dataScopedSQL(unscopedSql, tableName); + String sql = dataScopedSQL(unscopedSql, appTable); CachedRowSet rs = genericDataService.getCachedResultSet(sql, "SQL : " + sql); if (rs.size() == 0) - throw new DataTableNotFoundException(tableName, appTableId); + throw new DataTableNotFoundException(appTable, appTableId); } private String dataScopedSQL(String unscopedSQL, String appTable) { @@ -475,11 +482,6 @@ public class ReadWriteNonCoreDataServiceImpl implements } - private String getFullDatasetName(final String type, final String set) { - return type + "_x" + set; - } - - // only exclusively datatable functions below here @Override public void newDatatableEntry(String datatable, Long appTableId, Map queryParams) { @@ -502,6 +504,36 @@ public class ReadWriteNonCoreDataServiceImpl implements } + @Override + public void updateDatatableEntryOnetoOne(String datatable, Long appTableId, + Map queryParams) { + long startTime = System.currentTimeMillis(); + + GenericResultsetData grs = retrieveDataTableGenericResultSet(datatable, + appTableId, null, null); + + if (grs.getData().size() == 0) + throw new DataTableNotFoundException(datatable, appTableId); + + String sql = getUpdateSql(grs, datatable, + getFKField(getApplicationTableName(datatable)), appTableId, + queryParams); + + if (sql != null) + genericDataService.updateSQL(sql, "SQL: " + sql); + else + logger.info("No Changes"); + + long elapsed = System.currentTimeMillis() - startTime; + logger.info("FINISHING updateDatatableEntryOnetoOne: Elapsed Time: " + + elapsed + + " - datatable: " + + datatable + + " id: " + + appTableId); + + } + @Override public void deleteDatatableEntries(String datatable, Long appTableId) { long startTime = System.currentTimeMillis(); @@ -702,6 +734,27 @@ public class ReadWriteNonCoreDataServiceImpl implements } } + private String getApplicationTableName(String datatable) { + // TODO - only used for update... can probably remove this after as its + // a reread + String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + + datatable + "'"; + + CachedRowSet rs = genericDataService.getCachedResultSet(sql, "SQL : " + + sql); + + if (rs.size() == 0) + throw new DataTableNotFoundException(datatable); + + try { + rs.next(); + return rs.getString("application_table_name"); + } catch (SQLException e) { + throw new PlatformDataIntegrityException("error.msg.sql.error", + e.getMessage()); + } + } + private String getFKField(String applicationTableName) { return applicationTableName.substring(2) + "_id"; @@ -897,6 +950,97 @@ public class ReadWriteNonCoreDataServiceImpl implements return addSql; } + private String getUpdateSql(GenericResultsetData grs, String datatable, + String fkField, Long appTableId, Map queryParams) { + + Map affectedAndChangedColumns = getAffectedAndChangedColumns( + grs, queryParams); + + // just updating fields that have changed since pre-update read - though + // its possible these values are different from the page the user was + // looking at and even different from the current db values (if some + // other update got in quick) - would need a version field for + // completeness but its okay to take this risk with additional fields + // data + + if (affectedAndChangedColumns.size() == 0) + return null; + + String pValue = null; + String pValueWrite = ""; + String singleQuote = "'"; + boolean firstColumn = true; + String sql = "update `" + datatable + "` "; + + for (String key : affectedAndChangedColumns.keySet()) { + if (firstColumn) { + sql += " set "; + firstColumn = false; + } else { + sql += ", "; + } + + pValue = affectedAndChangedColumns.get(key); + if (StringUtils.isEmpty(pValue)) { + pValueWrite = "null"; + } else { + pValueWrite = singleQuote + + genericDataService.replace(pValue, singleQuote, + singleQuote + singleQuote) + singleQuote; + } + sql += "`" + key + "` = " + pValueWrite; + } + + sql += " where " + fkField + " = " + appTableId; + + return sql; + } + + private Map getAffectedAndChangedColumns( + GenericResultsetData grs, Map queryParams) { + + Map affectedColumns = getAffectedColumns( + grs.getColumnHeaders(), queryParams); + Map affectedAndChangedColumns = new HashMap(); + String columnValue; + + for (String key : affectedColumns.keySet()) { + columnValue = affectedColumns.get(key); + if (columnChanged(key, columnValue, grs)) { + affectedAndChangedColumns.put(key, columnValue); + } + } + + return affectedAndChangedColumns; + } + + private boolean columnChanged(String key, String keyValue, + GenericResultsetData grs) { + + List columnValues = grs.getData().get(0).getRow(); + + String columnValue = null; + for (int i = 0; i < grs.getColumnHeaders().size(); i++) { + + if (key.equals(grs.getColumnHeaders().get(i).getColumnName())) { + columnValue = columnValues.get(i); + + if (notTheSame(columnValue, keyValue)) { + logger.info("Difference - Column: " + key + + "- Current Value: " + columnValue + + " New Value: " + keyValue); + return true; + + } + return false; + } + } + + throw new PlatformDataIntegrityException( + "error.msg.invalid.columnName", "Parameter Column Name: " + key + + " not found"); + } + private Map getAffectedColumns( List columnHeaders, Map queryParams) { @@ -911,7 +1055,7 @@ public class ReadWriteNonCoreDataServiceImpl implements Map affectedColumns = new HashMap(); Set keys = queryParams.keySet(); for (String key : keys) { - // ignore any id field and matches incoming fields with and without + // ignores any id field and matches incoming fields with and without // underscores (spaces and underscores considered the same) if (!(key.equalsIgnoreCase("id"))) { notFound = true; @@ -925,6 +1069,7 @@ public class ReadWriteNonCoreDataServiceImpl implements if (queryParamColumnUnderscored .equalsIgnoreCase(columnHeaderUnderscored)) { pValue = queryParams.get(key); + validateColumn(columnHeader, pValue); affectedColumns.put(columnHeader.getColumnName(), pValue); notFound = false; @@ -942,6 +1087,69 @@ public class ReadWriteNonCoreDataServiceImpl implements return affectedColumns; } + private void validateColumn(ResultsetColumnHeader columnHeader, + String pValue) { + + if ((StringUtils.isEmpty(pValue)) + && (!(columnHeader.isColumnNullable()))) { + + List dataValidationErrors = new ArrayList(); + ApiParameterError error = ApiParameterError.parameterError( + "error.msg.column.mandatory", columnHeader.getColumnName(), + "Mandatory"); + dataValidationErrors.add(error); + throw new PlatformApiDataValidationException( + "validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); + } + + // check allowed values + if ((!StringUtils.isEmpty(pValue)) + && columnHeader.getColumnValuesNew().size() > 0) { + + List allowedValues = columnHeader + .getColumnValuesNew(); + if (columnHeader.getColumnType().equalsIgnoreCase("varchar")) { + for (ResultsetColumnValue allowedValue : allowedValues) { + if (pValue.equalsIgnoreCase(allowedValue.getValue())) + return; + } + List dataValidationErrors = new ArrayList(); + ApiParameterError error = ApiParameterError.parameterError( + "error.msg.invalid.columnValue", + columnHeader.getColumnName(), "Value :" + pValue + + "' not found in Allowed Value list"); + dataValidationErrors.add(error); + throw new PlatformApiDataValidationException( + "validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); + } + + if (columnHeader.getColumnType().equalsIgnoreCase("int")) { + for (ResultsetColumnValue allowedValue : allowedValues) { + if (pValue.equals(Integer.toString(allowedValue.getId()))) + return; + } + List dataValidationErrors = new ArrayList(); + ApiParameterError error = ApiParameterError.parameterError( + "error.msg.invalid.columnValue", + columnHeader.getColumnName(), "Value :" + pValue + + "' not found in Allowed Value list"); + dataValidationErrors.add(error); + throw new PlatformApiDataValidationException( + "validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); + } + + throw new PlatformDataIntegrityException( + "error.msg.invalid.columnType.", "Code: " + + columnHeader.getColumnName() + " - Invalid Type " + + columnHeader.getColumnType() + + " (neither varchar nor int)"); + } + + } + private String getDeleteEntriesSql(String datatable, String FKField, Long appTableId) { @@ -955,4 +1163,5 @@ public class ReadWriteNonCoreDataServiceImpl implements return "delete from `" + datatable + "` where `id` = " + datatableId; } + } \ No newline at end of file