FINERACT-2181: Split Datatable service

This commit is contained in:
Oleksii Novikov
2025-03-19 17:25:48 +01:00
committed by Adam Saghy
parent 123899eac1
commit 1d569d9ecf
29 changed files with 1045 additions and 892 deletions
@@ -0,0 +1,55 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Locale;
import org.apache.fineract.infrastructure.core.service.PagedLocalRequest;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.springframework.data.domain.Page;
public interface DatatableReadService {
List<DatatableData> retrieveDatatableNames(String appTable);
DatatableData retrieveDatatable(String datatable);
List<JsonObject> queryDataTable(@NotNull String datatable, @NotNull String columnName, String columnValue,
@NotNull String resultColumns);
Page<JsonObject> queryDataTableAdvanced(@NotNull String datatable, @NotNull PagedLocalRequest<AdvancedQueryData> pagedRequest);
boolean buildDataQueryEmbedded(@NotNull EntityTables entityTable, @NotNull String datatable, @NotNull AdvancedQueryData request,
@NotNull List<String> selectColumns, @NotNull StringBuilder select, @NotNull StringBuilder from, @NotNull StringBuilder where,
@NotNull List<Object> params, String mainAlias, String alias, String dateFormat, String dateTimeFormat, Locale locale);
GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
Long countDatatableEntries(String datatableName, Long appTableId, String foreignKeyColumn);
String getTableName(String Url);
String getDataTableName(String Url);
}
@@ -18,25 +18,11 @@
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Locale;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.service.PagedLocalRequest;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.springframework.data.domain.Page;
import org.springframework.security.access.prepost.PreAuthorize;
public interface ReadWriteNonCoreDataService {
List<DatatableData> retrieveDatatableNames(String appTable);
DatatableData retrieveDatatable(String datatable);
public interface DatatableWriteService {
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
void registerDatatable(JsonCommand command);
@@ -50,8 +36,6 @@ public interface ReadWriteNonCoreDataService {
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'DEREGISTER_DATATABLE')")
void deregisterDatatable(String datatable);
GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
CommandProcessingResult createDatatable(JsonCommand command);
void updateDatatable(String datatableName, JsonCommand command);
@@ -62,8 +46,6 @@ public interface ReadWriteNonCoreDataService {
CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, String json);
CommandProcessingResult createPPIEntry(String datatable, Long appTableId, JsonCommand command);
CommandProcessingResult updateDatatableEntryOneToOne(String datatable, Long appTableId, JsonCommand command);
CommandProcessingResult updateDatatableEntryOneToMany(String datatable, Long appTableId, Long datatableId, JsonCommand command);
@@ -72,18 +54,6 @@ public interface ReadWriteNonCoreDataService {
CommandProcessingResult deleteDatatableEntry(String datatable, Long appTableId, Long datatableId, JsonCommand command);
String getTableName(String Url);
CommandProcessingResult createPPIEntry(String datatable, Long appTableId, JsonCommand command);
String getDataTableName(String Url);
Long countDatatableEntries(String datatableName, Long appTableId, String foreignKeyColumn);
List<JsonObject> queryDataTable(@NotNull String datatable, @NotNull String columnName, String columnValue,
@NotNull String resultColumns);
Page<JsonObject> queryDataTableAdvanced(@NotNull String datatable, @NotNull PagedLocalRequest<AdvancedQueryData> pagedRequest);
boolean buildDataQueryEmbedded(@NotNull EntityTables entityTable, @NotNull String datatable, @NotNull AdvancedQueryData request,
@NotNull List<String> selectColumns, @NotNull StringBuilder select, @NotNull StringBuilder from, @NotNull StringBuilder where,
@NotNull List<Object> params, String mainAlias, String alias, String dateFormat, String dateTimeFormat, Locale locale);
}
@@ -55,8 +55,9 @@ import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer
import org.apache.fineract.infrastructure.core.service.PagedLocalRequest;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.springframework.data.domain.Page;
@@ -72,7 +73,8 @@ public class DatatablesApiResource {
private final PlatformSecurityContext context;
private final GenericDataService genericDataService;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
private final DatatableReadService datatableReadService;
private final DatatableWriteService datatableWriteService;
private final ToApiJsonSerializer<GenericResultsetData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@@ -87,7 +89,7 @@ public class DatatablesApiResource {
public String getDatatables(@QueryParam("apptable") @Parameter(description = "apptable") final String apptable,
@Context final UriInfo uriInfo) {
final List<DatatableData> result = this.readWriteNonCoreDataService.retrieveDatatableNames(apptable);
final List<DatatableData> result = this.datatableReadService.retrieveDatatableNames(apptable);
return this.toApiJsonSerializer.serialize(result);
}
@@ -184,7 +186,7 @@ public class DatatablesApiResource {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DatatablesApiResourceSwagger.PutDataTablesResponse.class))) })
public String deregisterDatatable(@PathParam("datatable") @Parameter(description = "datatable") final String datatable) {
this.readWriteNonCoreDataService.deregisterDatatable(datatable);
this.datatableWriteService.deregisterDatatable(datatable);
final CommandProcessingResult result = new CommandProcessingResultBuilder().withResourceIdAsString(datatable).build();
return this.toApiJsonSerializer.serialize(result);
}
@@ -199,7 +201,7 @@ public class DatatablesApiResource {
public String getDatatable(@PathParam("datatable") @Parameter(description = "datatable") final String datatable,
@Context final UriInfo uriInfo) {
final DatatableData result = this.readWriteNonCoreDataService.retrieveDatatable(datatable);
final DatatableData result = this.datatableReadService.retrieveDatatable(datatable);
return this.toApiJsonSerializer.serialize(result);
}
@@ -216,8 +218,7 @@ public class DatatablesApiResource {
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
final List<JsonObject> result = this.readWriteNonCoreDataService.queryDataTable(datatable, columnFilter, valueFilter,
resultColumns);
final List<JsonObject> result = this.datatableReadService.queryDataTable(datatable, columnFilter, valueFilter, resultColumns);
return this.toApiJsonSerializer.serialize(result);
}
@@ -231,7 +232,7 @@ public class DatatablesApiResource {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = List.class))) })
public String advancedQuery(@PathParam("datatable") @Parameter(description = "datatable") final String datatable,
PagedLocalRequest<AdvancedQueryData> queryRequest, @Context final UriInfo uriInfo) {
final Page<JsonObject> result = this.readWriteNonCoreDataService.queryDataTableAdvanced(datatable, queryRequest);
final Page<JsonObject> result = this.datatableReadService.queryDataTableAdvanced(datatable, queryRequest);
return this.toApiJsonSerializer.serialize(result);
}
@@ -254,8 +255,8 @@ public class DatatablesApiResource {
this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
final GenericResultsetData results = this.readWriteNonCoreDataService.retrieveDataTableGenericResultSet(datatable, apptableId,
order, null);
final GenericResultsetData results = this.datatableReadService.retrieveDataTableGenericResultSet(datatable, apptableId, order,
null);
String json = "";
final boolean genericResultSet = ApiParameterHelper.genericResultSet(uriInfo.getQueryParameters());
@@ -280,8 +281,8 @@ public class DatatablesApiResource {
this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
final GenericResultsetData results = this.readWriteNonCoreDataService.retrieveDataTableGenericResultSet(datatable, apptableId,
order, datatableId);
final GenericResultsetData results = this.datatableReadService.retrieveDataTableGenericResultSet(datatable, apptableId, order,
datatableId);
String json = "";
if (genericResultSet) {
@@ -22,7 +22,7 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -30,12 +30,12 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
public class CreateDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return writePlatformService.createDatatable(command);
return datatableWriteService.createDatatable(command);
}
}
@@ -22,7 +22,7 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -30,12 +30,12 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
public class CreateDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return writePlatformService.createNewDatatableEntry(command.getEntityName(), command.entityId(), command);
return datatableWriteService.createNewDatatableEntry(command.getEntityName(), command.entityId(), command);
}
}
@@ -18,30 +18,27 @@
*/
package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class DeleteDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
@Autowired
public DeleteDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
final String datatableName = command.getUrl().replaceAll("/datatables/", "");
this.writePlatformService.deleteDatatable(datatableName);
datatableWriteService.deleteDatatable(datatableName);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
}
}
@@ -18,27 +18,24 @@
*/
package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class DeleteOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
@Autowired
public DeleteOneToManyDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.deleteDatatableEntry(command.getEntityName(), command.entityId(), command.subentityId(), command);
return datatableWriteService.deleteDatatableEntry(command.getEntityName(), command.entityId(), command.subentityId(), command);
}
}
@@ -18,27 +18,24 @@
*/
package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class DeleteOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
@Autowired
public DeleteOneToOneDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.deleteDatatableEntries(command.getEntityName(), command.entityId(), command);
return datatableWriteService.deleteDatatableEntries(command.getEntityName(), command.entityId(), command);
}
}
@@ -18,32 +18,28 @@
*/
package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class RegisterDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
@Autowired
public RegisterDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
private final DatatableWriteService datatableWriteService;
private final DatatableReadService datatableReadService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
datatableWriteService.registerDatatable(command);
this.writePlatformService.registerDatatable(command);
return new CommandProcessingResultBuilder().withResourceIdAsString(this.writePlatformService.getDataTableName(command.getUrl()))
.build();
return new CommandProcessingResultBuilder().withResourceIdAsString(datatableReadService.getDataTableName(command.getUrl())).build();
}
}
@@ -23,7 +23,7 @@ import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -31,16 +31,16 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
public class UpdateDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
final String datatableName = command.getUrl().replaceAll("/datatables/", "");
this.writePlatformService.updateDatatable(datatableName, command);
datatableWriteService.updateDatatable(datatableName, command);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
}
}
@@ -18,30 +18,26 @@
*/
package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UpdateOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
@Autowired
public UpdateOneToManyDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.updateDatatableEntryOneToMany(command.getEntityName(), command.entityId(), command.subentityId(),
return this.datatableWriteService.updateDatatableEntryOneToMany(command.getEntityName(), command.entityId(), command.subentityId(),
command);
}
}
@@ -18,30 +18,25 @@
*/
package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UpdateOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService;
@Autowired
public UpdateOneToOneDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
private final DatatableWriteService datatableWriteService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
final CommandProcessingResult commandProcessingResult = this.writePlatformService
final CommandProcessingResult commandProcessingResult = this.datatableWriteService
.updateDatatableEntryOneToOne(command.getEntityName(), command.entityId(), command);
return new CommandProcessingResultBuilder() //
@@ -55,4 +50,5 @@ public class UpdateOneToOneDatatableEntryCommandHandler implements NewCommandSou
.with(commandProcessingResult.getChanges()) //
.build();
}
}
@@ -0,0 +1,282 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import static java.util.Arrays.asList;
import static org.apache.fineract.infrastructure.core.service.database.SqlOperator.EQ;
import com.google.common.base.Splitter;
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.PagedLocalRequest;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.apache.fineract.portfolio.search.data.ColumnFilterData;
import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@RequiredArgsConstructor
public class DatatableReadServiceImpl implements DatatableReadService {
private static final String APPLICATION_TABLE_NAME = "application_table_name";
private final JdbcTemplate jdbcTemplate;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final PlatformSecurityContext context;
private final GenericDataService genericDataService;
private final DataTableValidator dataTableValidator;
private final SqlValidator sqlValidator;
private final SearchUtil searchUtil;
private final DatatableUtil datatableUtil;
@Override
public List<DatatableData> retrieveDatatableNames(final String appTable) {
// PERMITTED datatables
String sql = "select application_table_name, registered_table_name, entity_subtype from x_registered_table where exists"
+ " (select 'f' from m_appuser_role ur join m_role r on r.id = ur.role_id"
+ " left join m_role_permission rp on rp.role_id = r.id left join m_permission p on p.id = rp.permission_id"
+ " where ur.appuser_id = ? and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat"
+ "('READ_', registered_table_name))) ";
Object[] params;
if (appTable != null) {
sql = sql + " and application_table_name like ? ";
params = new Object[] { this.context.authenticatedUser().getId(), appTable };
} else {
params = new Object[] { this.context.authenticatedUser().getId() };
}
sql = sql + " order by application_table_name, registered_table_name";
final List<DatatableData> datatables = new ArrayList<>();
final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, params); // NOSONAR
while (rowSet.next()) {
final String appTableName = rowSet.getString(APPLICATION_TABLE_NAME);
final String registeredDatatableName = rowSet.getString("registered_table_name");
final String entitySubType = rowSet.getString("entity_subtype");
final List<ResultsetColumnHeaderData> columnHeaderData = genericDataService.fillResultsetColumnHeaders(registeredDatatableName);
datatables.add(DatatableData.create(appTableName, registeredDatatableName, entitySubType, columnHeaderData));
}
return datatables;
}
@Override
public DatatableData retrieveDatatable(final String datatable) {
// PERMITTED datatables
sqlValidator.validate(datatable);
final String sql = "select application_table_name, registered_table_name, entity_subtype from x_registered_table "
+ " where exists (select 'f' from m_appuser_role ur join m_role r on r.id = ur.role_id"
+ " left join m_role_permission rp on rp.role_id = r.id left join m_permission p on p.id = rp.permission_id"
+ " where ur.appuser_id = ? and registered_table_name=? and (p.code in ('ALL_FUNCTIONS', "
+ "'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
+ " order by application_table_name, registered_table_name";
DatatableData datatableData = null;
final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { this.context.authenticatedUser().getId(), datatable }); // NOSONAR
if (rowSet.next()) {
final String appTableName = rowSet.getString(APPLICATION_TABLE_NAME);
final String registeredDatatableName = rowSet.getString("registered_table_name");
final String entitySubType = rowSet.getString("entity_subtype");
final List<ResultsetColumnHeaderData> columnHeaderData = this.genericDataService
.fillResultsetColumnHeaders(registeredDatatableName);
datatableData = DatatableData.create(appTableName, registeredDatatableName, entitySubType, columnHeaderData);
}
return datatableData;
}
@Override
public List<JsonObject> queryDataTable(@NotNull String datatable, @NotNull String columnName, String columnValueString,
@NotNull String resultColumnsString) {
datatable = datatableUtil.validateDatatableRegistered(datatable);
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
List<String> resultColumns = asList(resultColumnsString.split(","));
List<String> selectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false);
ResultsetColumnHeaderData column = searchUtil.validateToJdbcColumn(columnName, headersByName, false);
Object columnValue = searchUtil.parseJdbcColumnValue(column, columnValueString, null, null, null, false, sqlGenerator);
String sql = sqlGenerator.buildSelect(selectColumns, null, false) + " " + sqlGenerator.buildFrom(datatable, null, false) + " WHERE "
+ EQ.formatPlaceholder(sqlGenerator, column.getColumnName(), 1, null);
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, columnValue); // NOSONAR
List<JsonObject> results = new ArrayList<>();
while (rowSet.next()) {
searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
}
return results;
}
@Override
public Page<JsonObject> queryDataTableAdvanced(@NotNull String datatable, @NotNull PagedLocalRequest<AdvancedQueryData> pagedRequest) {
datatable = datatableUtil.validateDatatableRegistered(datatable);
context.authenticatedUser().validateHasDatatableReadPermission(datatable);
AdvancedQueryData request = pagedRequest.getRequest().orElseThrow();
dataTableValidator.validateTableSearch(request);
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
String pkColumn = searchUtil.getFiltered(headersByName.values(), ResultsetColumnHeaderData::getIsColumnPrimaryKey).getColumnName();
List<ColumnFilterData> columnFilters = request.getNonNullFilters();
columnFilters.forEach(e -> e.setColumn(searchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false)));
List<String> resultColumns = request.getNonNullResultColumns();
List<String> selectColumns;
if (resultColumns.isEmpty()) {
resultColumns.add(pkColumn);
selectColumns = new ArrayList<>();
selectColumns.add(pkColumn);
} else {
selectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, false);
}
PageRequest pageable = pagedRequest.toPageable();
PageRequest sortPageable;
if (pageable.getSort().isSorted()) {
List<Sort.Order> orders = pageable.getSort().toList();
sortPageable = pageable.withSort(Sort.by(orders.stream()
.map(e -> e.withProperty(searchUtil.validateToJdbcColumnName(e.getProperty(), headersByName, false))).toList()));
} else {
pageable = pageable.withSort(Sort.Direction.DESC, pkColumn);
sortPageable = pageable;
}
String dateFormat = pagedRequest.getDateFormat();
String dateTimeFormat = pagedRequest.getDateTimeFormat();
Locale locale = pagedRequest.getLocaleObject();
String select = sqlGenerator.buildSelect(selectColumns, null, false);
String from = " " + sqlGenerator.buildFrom(datatable, null, false);
StringBuilder where = new StringBuilder();
ArrayList<Object> params = new ArrayList<>();
searchUtil.buildQueryCondition(columnFilters, where, params, null, headersByName, dateFormat, dateTimeFormat, locale, false,
sqlGenerator);
List<JsonObject> results = new ArrayList<>();
Object[] args = params.toArray();
// Execute the count Query
String countQuery = "SELECT COUNT(*)" + from + where;
Integer totalElements = jdbcTemplate.queryForObject(countQuery, Integer.class, args); // NOSONAR
if (totalElements == null || totalElements == 0) {
return PageableExecutionUtils.getPage(results, pageable, () -> 0);
}
StringBuilder query = new StringBuilder().append(select).append(from).append(where);
query.append(" ").append(sqlGenerator.buildOrderBy(sortPageable.getSort().toList(), null, false));
if (pageable.isPaged()) {
query.append(" ").append(sqlGenerator.limit(pageable.getPageSize(), (int) pageable.getOffset()));
}
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(query.toString(), args);
while (rowSet.next()) {
searchUtil.extractJsonResult(rowSet, selectColumns, resultColumns, results);
}
return PageableExecutionUtils.getPage(results, pageable, () -> totalElements);
}
@Override
public boolean buildDataQueryEmbedded(@NotNull EntityTables entityTable, @NotNull String datatable, @NotNull AdvancedQueryData request,
@NotNull List<String> selectColumns, @NotNull StringBuilder select, @NotNull StringBuilder from, @NotNull StringBuilder where,
@NotNull List<Object> params, String mainAlias, String alias, String dateFormat, String dateTimeFormat, Locale locale) {
List<String> resultColumns = request.getResultColumns();
List<ColumnFilterData> columnFilters = request.getColumnFilters();
if ((resultColumns == null || resultColumns.isEmpty()) && (columnFilters == null || columnFilters.isEmpty())) {
return false;
}
datatable = datatableUtil.validateDatatableRegistered(datatable);
context.authenticatedUser().validateHasDatatableReadPermission(datatable);
Map<String, ResultsetColumnHeaderData> headersByName = searchUtil
.mapHeadersToName(genericDataService.fillResultsetColumnHeaders(datatable));
List<String> thisSelectColumns = searchUtil.validateToJdbcColumnNames(resultColumns, headersByName, true);
if (columnFilters != null) {
columnFilters.forEach(e -> e.setColumn(searchUtil.validateToJdbcColumnName(e.getColumn(), headersByName, false)));
}
select.append(sqlGenerator.buildSelect(thisSelectColumns, alias, true));
selectColumns.addAll(thisSelectColumns);
String joinType = "LEFT";
if (searchUtil.buildQueryCondition(columnFilters, where, params, alias, headersByName, dateFormat, dateTimeFormat, locale, true,
sqlGenerator)) {
joinType = null; // INNER
}
from.append(sqlGenerator.buildJoin(datatable, alias, entityTable.getForeignKeyColumnNameOnDatatable(), mainAlias,
entityTable.getRefColumn(), joinType));
return true;
}
@Override
@Transactional(readOnly = true)
public GenericResultsetData retrieveDataTableGenericResultSet(final String dataTableName, final Long appTableId, final String order,
final Long id) {
final EntityTables entityTable = datatableUtil.queryForApplicationEntity(dataTableName);
datatableUtil.checkMainResourceExistsWithinScope(entityTable, appTableId);
return datatableUtil.retrieveDataTableGenericResultSet(entityTable, dataTableName, appTableId, order, id);
}
@Override
public Long countDatatableEntries(final String datatableName, final Long appTableId, String foreignKeyColumn) {
final String sqlString = "SELECT COUNT(" + sqlGenerator.escape(foreignKeyColumn) + ") FROM " + sqlGenerator.escape(datatableName)
+ " WHERE " + sqlGenerator.escape(foreignKeyColumn) + " = " + appTableId;
return this.jdbcTemplate.queryForObject(sqlString, Long.class); // NOSONAR
}
@Override
public String getDataTableName(String url) {
List<String> urlParts = Splitter.on('/').splitToList(url);
return urlParts.get(3);
}
@Override
public String getTableName(String url) {
List<String> urlParts = Splitter.on('/').splitToList(url);
return urlParts.get(4);
}
}
@@ -0,0 +1,227 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import static org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer.DATATABLE_NAME_REGEX_PATTERN;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_NAME;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.TABLE_FIELD_ID;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.TABLE_REGISTERED_TABLE;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableSystemErrorException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class DatatableUtil {
private static final String APPLICATION_TABLE_NAME = "application_table_name";
private final SearchUtil searchUtil;
private final JdbcTemplate jdbcTemplate;
private final SqlValidator sqlValidator;
private final PlatformSecurityContext context;
private final GenericDataService genericDataService;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final ColumnValidator columnValidator;
public boolean isMultirowDatatable(final List<ResultsetColumnHeaderData> columnHeaders) {
return searchUtil.findFiltered(columnHeaders, e -> e.isNamed(TABLE_FIELD_ID)) != null;
}
public String getFKField(EntityTables entityTable) {
return entityTable.getForeignKeyColumnNameOnDatatable();
}
public String validateDatatableRegistered(String datatable) {
validateDatatableName(datatable);
if (!isRegisteredDataTable(datatable)) {
throw new DatatableNotFoundException(datatable);
}
return datatable;
}
public 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);
}
sqlValidator.validate(name);
}
public EntityTables resolveEntity(final String entityName) {
EntityTables entityTable = EntityTables.fromEntityName(entityName);
if (entityTable == null) {
throw new PlatformDataIntegrityException("error.msg.invalid.application.table", "Invalid Datatable entity: " + entityName,
API_FIELD_NAME, entityName);
}
return entityTable;
}
@NotNull
public EntityTables queryForApplicationEntity(final String datatable) {
sqlValidator.validate(datatable);
final String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = ?";
final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, datatable); // NOSONAR
String applicationTableName;
if (rowSet.next()) {
applicationTableName = rowSet.getString(APPLICATION_TABLE_NAME);
} else {
throw new DatatableNotFoundException(datatable);
}
return resolveEntity(applicationTableName);
}
public CommandProcessingResult checkMainResourceExistsWithinScope(@NotNull EntityTables entityTable, final Long appTableId) {
final String sql = dataScopedSQL(entityTable, appTableId);
log.debug("data scoped sql: {}", sql);
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql);
if (!rs.next()) {
throw new DatatableNotFoundException(entityTable, appTableId);
}
final Long officeId = (Long) rs.getObject("officeId");
final Long groupId = (Long) rs.getObject("groupId");
final Long clientId = (Long) rs.getObject("clientId");
final Long savingsId = (Long) rs.getObject("savingsId");
final Long loanId = (Long) rs.getObject("loanId");
final Long transactionId = (Long) rs.getObject("transactionId");
final Long entityId = (Long) rs.getObject("entityId");
if (rs.next()) {
throw new DatatableSystemErrorException("System Error: More than one row returned from data scoping query");
}
return new CommandProcessingResultBuilder() //
.withOfficeId(officeId) //
.withGroupId(groupId) //
.withClientId(clientId) //
.withSavingsId(savingsId) //
.withLoanId(loanId).withTransactionId(transactionId == null ? null : String.valueOf(transactionId)).withEntityId(entityId)//
.build();
}
public String dataScopedSQL(@NotNull EntityTables entityTable, final Long appTableId) {
// unfortunately have to, one way or another, be able to restrict data to the users office hierarchy. Here, a
// few key tables are done. But if additional fields are needed on other tables the same pattern applies
final AppUser currentUser = this.context.authenticatedUser();
String officeHierarchy = currentUser.getOffice().getHierarchy();
// m_loan and m_savings_account are connected to an m_office through either an m_client or an m_group If both it
// means it relates to an m_client that is in a group (still an m_client account)
return switch (entityTable) {
case LOAN -> "select distinct x.* from ( "
+ "(select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as transactionId, null as entityId from m_loan l "
+ getClientOfficeJoinCondition(officeHierarchy, "l") + " where l.id = " + appTableId + ")" + " union all "
+ "(select o.id as officeId, l.group_id as groupId, l.client_id as clientId, null as savingsId, l.id as loanId, null as transactionId, null as entityId from m_loan l "
+ getGroupOfficeJoinCondition(officeHierarchy, "l") + " where l.id = " + appTableId + ")" + " ) as x";
case SAVINGS -> "select distinct x.* from ( "
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as transactionId, null as entityId "
+ "from m_savings_account s " + getClientOfficeJoinCondition(officeHierarchy, "s") + " where s.id = " + appTableId + ")"
+ " union all "
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, null as transactionId, null as entityId "
+ "from m_savings_account s " + getGroupOfficeJoinCondition(officeHierarchy, "s") + " where s.id = " + appTableId + ")"
+ " ) as x";
case SAVINGS_TRANSACTION -> "select distinct x.* from ( "
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, t.id as transactionId, null as entityId "
+ "from m_savings_account_transaction t join m_savings_account s on t.savings_account_id = s.id "
+ getClientOfficeJoinCondition(officeHierarchy, "s") + " where t.id = " + appTableId + ")" + " union all "
+ "(select o.id as officeId, s.group_id as groupId, s.client_id as clientId, s.id as savingsId, null as loanId, t.id as transactionId, null as entityId "
+ "from m_savings_account_transaction t join m_savings_account s on t.savings_account_id = s.id "
+ getGroupOfficeJoinCondition(officeHierarchy, "s") + " where t.id = " + appTableId + ")" + " ) as x";
case CLIENT ->
"select o.id as officeId, null as groupId, c.id as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_client c "
+ getOfficeJoinCondition(officeHierarchy, "c") + " where c.id = " + appTableId;
case GROUP, CENTER ->
"select o.id as officeId, g.id as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_group g "
+ getOfficeJoinCondition(officeHierarchy, "g") + " where g.id = " + appTableId;
case OFFICE ->
"select o.id as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, null as entityId from m_office o "
+ "where o.hierarchy like '" + officeHierarchy + "%'" + " and o.id = " + appTableId;
case LOAN_PRODUCT, SAVINGS_PRODUCT, SHARE_PRODUCT ->
"select null as officeId, null as groupId, null as clientId, null as savingsId, null as loanId, null as transactionId, p.id as entityId from "
+ entityTable.getName() + " as p WHERE p.id = " + appTableId;
default -> throw new PlatformDataIntegrityException("error.msg.invalid.dataScopeCriteria",
"Application Table: " + entityTable.getName() + " not catered for in data Scoping");
};
}
public String getOfficeJoinCondition(String officeHierarchy, String joinTableAlias) {
return " join m_office o on o.id = " + joinTableAlias + ".office_id and o.hierarchy like '" + officeHierarchy + "%' ";
}
public String getGroupOfficeJoinCondition(String officeHierarchy, String appTableAlias) {
return " join m_group g on g.id = " + appTableAlias + ".group_id " + getOfficeJoinCondition(officeHierarchy, "g");
}
public String getClientOfficeJoinCondition(String officeHierarchy, String appTableAlias) {
return " join m_client c on c.id = " + appTableAlias + ".client_id " + getOfficeJoinCondition(officeHierarchy, "c");
}
public GenericResultsetData retrieveDataTableGenericResultSet(final EntityTables entityTable, final String dataTableName,
final Long appTableId, final String order, final Long id) {
final List<ResultsetColumnHeaderData> columnHeaders = genericDataService.fillResultsetColumnHeaders(dataTableName);
final boolean multiRow = isMultirowDatatable(columnHeaders);
String whereClause = getFKField(entityTable) + " = " + appTableId;
sqlValidator.validate(whereClause);
String sql = "select * from " + sqlGenerator.escape(dataTableName) + " where " + whereClause;
// id only used for reading a specific entry that belongs to appTableId (in a one to many datatable)
if (multiRow && id != null) {
sql = sql + " and " + TABLE_FIELD_ID + " = " + id;
}
if (StringUtils.isNotBlank(order)) {
columnValidator.validateSqlInjection(sql, order);
sql = sql + " order by " + order;
}
final List<ResultsetRowData> result = genericDataService.fillResultsetRowData(sql, columnHeaders);
return new GenericResultsetData(columnHeaders, result);
}
private boolean isRegisteredDataTable(final String datatable) {
final String sql = "SELECT COUNT(application_table_name) FROM " + TABLE_REGISTERED_TABLE + " WHERE registered_table_name = ?";
final Integer count = jdbcTemplate.queryForObject(sql, Integer.class, datatable);
return count != null && count > 0;
}
}
@@ -30,7 +30,6 @@ 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.core.service.database.JdbcJavaType;
import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableCheckStatusData;
@@ -46,7 +45,6 @@ import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@@ -55,33 +53,28 @@ 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;
private final EntityDatatableChecksRepository entityDatatableChecksRepository;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
private final DatatableReadService datatableReadService;
private final LoanProductReadPlatformService loanProductReadPlatformService;
private final SavingsProductReadPlatformService savingsProductReadPlatformService;
private final PaginationHelper paginationHelper;
@Autowired
public EntityDatatableChecksReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseTypeResolver databaseTypeResolver,
DatabaseSpecificSQLGenerator sqlGenerator, final LoanProductReadPlatformService loanProductReadPlatformService,
final SavingsProductReadPlatformService savingsProductReadPlatformService,
final EntityDatatableChecksRepository entityDatatableChecksRepository,
final ReadWriteNonCoreDataService readWriteNonCoreDataService, PaginationHelper paginationHelper) {
public EntityDatatableChecksReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final DatabaseSpecificSQLGenerator sqlGenerator,
final EntityDatatableChecksRepository entityDatatableChecksRepository, final DatatableReadService datatableReadService,
final LoanProductReadPlatformService loanProductReadPlatformService,
final SavingsProductReadPlatformService savingsProductReadPlatformService, final PaginationHelper paginationHelper) {
this.jdbcTemplate = jdbcTemplate;
this.databaseTypeResolver = databaseTypeResolver;
this.sqlGenerator = sqlGenerator;
this.registerDataTableMapper = new RegisterDataTableMapper();
this.entityDataTableChecksMapper = new EntityDataTableChecksMapper();
this.entityDatatableChecksRepository = entityDatatableChecksRepository;
this.datatableReadService = datatableReadService;
this.loanProductReadPlatformService = loanProductReadPlatformService;
this.savingsProductReadPlatformService = savingsProductReadPlatformService;
this.entityDatatableChecksRepository = entityDatatableChecksRepository;
this.readWriteNonCoreDataService = readWriteNonCoreDataService;
this.paginationHelper = paginationHelper;
this.registerDataTableMapper = new RegisterDataTableMapper();
this.entityDataTableChecksMapper = new EntityDataTableChecksMapper();
}
@Override
@@ -134,7 +127,7 @@ public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatat
if (tableRequiredBeforeAction != null && tableRequiredBeforeAction.size() > 0) {
List<DatatableData> ret = new ArrayList<>();
for (EntityDatatableChecks t : tableRequiredBeforeAction) {
ret.add(this.readWriteNonCoreDataService.retrieveDatatable(t.getDatatableName()));
ret.add(this.datatableReadService.retrieveDatatable(t.getDatatableName()));
}
return ret;
}
@@ -60,7 +60,8 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
private final PlatformSecurityContext context;
private final EntityDatatableChecksDataValidator fromApiJsonDeserializer;
private final EntityDatatableChecksRepository entityDatatableChecksRepository;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
private final DatatableReadService datatableReadService;
private final DatatableWriteService datatableWriteService;
private final LoanProductReadPlatformService loanProductReadPlatformService;
private final SavingsProductReadPlatformService savingsProductReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
@@ -77,7 +78,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
// check if the datatable is linked to the entity
String datatableName = command.stringValueOfParameterNamed("datatableName");
DatatableData datatableData = this.readWriteNonCoreDataService.retrieveDatatable(datatableName);
DatatableData datatableData = this.datatableReadService.retrieveDatatable(datatableName);
if (datatableData == null) {
throw new DatatableNotFoundException(datatableName);
@@ -154,7 +155,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
for (EntityDatatableChecks t : tableRequiredBeforeClientActivation) {
final String datatableName = t.getDatatableName();
final Long countEntries = readWriteNonCoreDataService.countDatatableEntries(datatableName, entityId, foreignKeyColumn);
final Long countEntries = datatableReadService.countDatatableEntries(datatableName, entityId, foreignKeyColumn);
log.debug("The are {} entries in the table {}", countEntries, datatableName);
if (countEntries.intValue() == 0) {
@@ -182,7 +183,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
for (EntityDatatableChecks t : tableRequiredBeforAction) {
final String datatableName = t.getDatatableName();
final Long countEntries = readWriteNonCoreDataService.countDatatableEntries(datatableName, entityId, foreignKeyColumn);
final Long countEntries = datatableReadService.countDatatableEntries(datatableName, entityId, foreignKeyColumn);
log.debug("The are {} entries in the table {}", countEntries, datatableName);
if (countEntries.intValue() == 0) {
@@ -220,7 +221,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
isMakerCheckerEnabled = true;
}
try {
this.readWriteNonCoreDataService.createNewDatatableEntry(datatableName, entityId, datatableData.toString());
datatableWriteService.createNewDatatableEntry(datatableName, entityId, datatableData.toString());
} catch (PlatformApiDataValidationException e) {
for (ApiParameterError error : e.getErrors()) {
error.setParameterName("datatables." + datatableName + "." + error.getParameterName());
@@ -26,13 +26,15 @@ import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecific
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableKeywordGenerator;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadServiceImpl;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableUtil;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteServiceImpl;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataServiceImpl;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@@ -45,17 +47,27 @@ public class DataQueriesAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ReadWriteNonCoreDataService readWriteNonCoreDataService(final JdbcTemplate jdbcTemplate,
final DatabaseTypeResolver databaseTypeResolver, final DatabaseSpecificSQLGenerator sqlGenerator,
final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService,
final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final ConfigurationDomainService configurationDomainService, final CodeReadPlatformService codeReadPlatformService,
final DataTableValidator dataTableValidator, final ColumnValidator columnValidator,
final NamedParameterJdbcTemplate namedParameterJdbcTemplate, DatatableKeywordGenerator datatableKeywordGenerator,
SqlValidator sqlValidator, SearchUtil searchUtil, final BusinessEventNotifierService businessEventNotifierService) {
return new ReadWriteNonCoreDataServiceImpl(jdbcTemplate, databaseTypeResolver, sqlGenerator, context, fromJsonHelper,
genericDataService, fromApiJsonDeserializer, configurationDomainService, codeReadPlatformService, dataTableValidator,
columnValidator, namedParameterJdbcTemplate, datatableKeywordGenerator, sqlValidator, searchUtil,
businessEventNotifierService);
public DatatableReadService datatableReadService(final JdbcTemplate jdbcTemplate, final DatabaseSpecificSQLGenerator sqlGenerator,
final PlatformSecurityContext context, final GenericDataService genericDataService, final DataTableValidator dataTableValidator,
final SqlValidator sqlValidator, final SearchUtil searchUtil, final DatatableUtil datatableUtil) {
return new DatatableReadServiceImpl(jdbcTemplate, sqlGenerator, context, genericDataService, dataTableValidator, sqlValidator,
searchUtil, datatableUtil);
}
@Bean
@ConditionalOnMissingBean
public DatatableWriteService datatableWriteService(final JdbcTemplate jdbcTemplate, final DatabaseTypeResolver databaseTypeResolver,
final DatabaseSpecificSQLGenerator sqlGenerator, final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
final GenericDataService genericDataService, final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final ConfigurationDomainService configurationDomainService, final CodeReadPlatformService codeReadPlatformService,
final DataTableValidator dataTableValidator, final NamedParameterJdbcTemplate namedParameterJdbcTemplate,
final DatatableKeywordGenerator datatableKeywordGenerator, final SearchUtil searchUtil,
final BusinessEventNotifierService businessEventNotifierService, final DatatableReadService datatableReadService,
final DatatableUtil datatableUtil) {
return new DatatableWriteServiceImpl(jdbcTemplate, databaseTypeResolver, sqlGenerator, context, fromJsonHelper, genericDataService,
fromApiJsonDeserializer, configurationDomainService, codeReadPlatformService, dataTableValidator,
namedParameterJdbcTemplate, datatableKeywordGenerator, searchUtil, businessEventNotifierService, datatableReadService,
datatableUtil);
}
}
@@ -26,8 +26,8 @@ import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
@@ -46,7 +46,7 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
private final JdbcTemplate jdbcTemplate;
private final SqlValidator sqlValidator;
private final GenericDataService genericDataService;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
private final DatatableReadService datatableReadService;
@Override
public List<SurveyDataTableData> retrieveAllSurveys() {
@@ -183,7 +183,7 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
@Override
public GenericResultsetData retrieveSurveyEntry(String surveyName, Long clientId, Long entryId) {
return readWriteNonCoreDataService.retrieveDataTableGenericResultSet(surveyName, clientId, null, entryId);
return datatableReadService.retrieveDataTableGenericResultSet(surveyName, clientId, null, entryId);
}
}
@@ -24,7 +24,8 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -35,16 +36,16 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
public class WriteSurveyServiceImpl implements WriteSurveyService {
private final ReadWriteNonCoreDataService readWriteNonCoreDataService;
private final DatatableReadService datatableReadService;
private final DatatableWriteService datatableWriteService;
private final DatabaseSpecificSQLGenerator sqlGenerator;
@Override
@Transactional
public CommandProcessingResult registerSurvey(JsonCommand command) {
final String dataTableName = this.readWriteNonCoreDataService.getDataTableName(command.getUrl());
final String dataTableName = datatableReadService.getDataTableName(command.getUrl());
final String permissionSql = this.getPermissionSql(dataTableName);
this.readWriteNonCoreDataService.registerDatatable(command, permissionSql);
datatableWriteService.registerDatatable(command, permissionSql);
return CommandProcessingResult.commandOnlyResult(command.commandId());
@@ -73,8 +74,7 @@ public class WriteSurveyServiceImpl implements WriteSurveyService {
@Transactional
@Override
public CommandProcessingResult fullFillSurvey(final String dataTableName, final Long appTableId, final JsonCommand command) {
return readWriteNonCoreDataService.createPPIEntry(dataTableName, appTableId, command);
return datatableWriteService.createPPIEntry(dataTableName, appTableId, command);
}
}
@@ -38,8 +38,8 @@ import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
@@ -66,7 +66,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
private final PlatformSecurityContext context;
private final GenericDataService genericDataService;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final ReadWriteNonCoreDataService datatableService;
private final DatatableReadService datatableReadService;
private final DataTableValidator dataTableValidator;
private final JdbcTemplate jdbcTemplate;
private final SearchUtil searchUtil;
@@ -239,7 +239,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
ArrayList<Object> dataParams = new ArrayList<>();
for (int i = 0; i < datatableQueries.size(); i++) {
TableQueryData tableQuery = datatableQueries.get(i);
boolean added = datatableService.buildDataQueryEmbedded(EntityTables.SAVINGS_TRANSACTION, tableQuery.getTable(),
boolean added = datatableReadService.buildDataQueryEmbedded(EntityTables.SAVINGS_TRANSACTION, tableQuery.getTable(),
tableQuery.getQuery(), selectColumns, dataSelect, dataFrom, dataWhere, dataParams, alias, ("d" + i), dateFormat,
dateTimeFormat, locale);
if (added) {
@@ -30,10 +30,10 @@ import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -154,7 +154,7 @@ public class SavingsConfiguration {
@Bean
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator, ReadWriteNonCoreDataService datatableService,
GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator, DatatableReadService datatableService,
DataTableValidator dataTableValidator, JdbcTemplate jdbcTemplate, SearchUtil searchUtil) {
return new SavingsAccountTransactionsSearchServiceImpl(context, genericDataService, sqlGenerator, datatableService,
dataTableValidator, jdbcTemplate, searchUtil);
@@ -18,11 +18,13 @@
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -36,66 +38,64 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.codes.service.CodeReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
import org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
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.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.datatable.DatatableEntryBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
import org.apache.fineract.infrastructure.security.utils.DefaultSqlValidator;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.apache.fineract.useradministration.domain.AppUser;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;
@ExtendWith(MockitoExtension.class)
public class DatatableBusinessEventTest {
@MockitoSettings(strictness = Strictness.LENIENT)
class DatatableBusinessEventTest {
private static final String DATATABLE_NAME = "test_loan_data";
private static final long APP_TABLE_ID = 1L;
private static final long DATATABLE_ID = 1L;
@Mock
private PlatformSecurityContext context;
@Mock
private BusinessEventNotifierService businessEventNotifierService;
@Mock
private JdbcTemplate jdbcTemplate;
@Mock
private GenericDataService genericDataService;
@Mock
private SearchUtil searchUtil;
@Mock
private DatabaseTypeResolver databaseTypeResolver;
@Mock
private DatabaseSpecificSQLGenerator sqlGenerator;
@Mock
private FineractPlatformTenantConnection tenantConnection;
@Mock
private DefaultSqlValidator sqlValidator;
private PlatformSecurityContext context;
@Mock
private FromJsonHelper fromJsonHelper;
@Mock
private GenericDataService genericDataService;
@Mock
private DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer;
@Mock
private ConfigurationDomainService configurationDomainService;
@@ -104,38 +104,73 @@ public class DatatableBusinessEventTest {
@Mock
private DataTableValidator dataTableValidator;
@Mock
private ColumnValidator columnValidator;
@Mock
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Mock
private DatatableKeywordGenerator datatableKeywordGenerator;
@Mock
private SearchUtil searchUtil;
@Mock
private BusinessEventNotifierService businessEventNotifierService;
@Mock
private DatatableReadService datatableReadService;
@Mock
private DatatableUtil datatableUtil;
@InjectMocks
private ReadWriteNonCoreDataServiceImpl underTest;
@Captor
private ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor;
private static String DATATABLE_NAME = "test_loan_data";
private DatatableWriteServiceImpl underTest;
@BeforeEach
public void setUp() {
void setUp() {
setupThreadLocalContext();
setupSecurityContext();
setupEntityTable();
setupCommandProcessingResult();
}
@AfterEach
void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
void shouldNotifyBusinessEventAfterCreatingNewDatatableEntry() {
setupCreateDatatableEntryMocks();
JsonCommand command = createJsonCommand("{}", APP_TABLE_ID);
underTest.createNewDatatableEntry(DATATABLE_NAME, APP_TABLE_ID, command);
verifyBusinessEvent(APP_TABLE_ID);
}
@Test
void shouldNotifyBusinessEventAfterUpdatingDatatableEntry() {
setupUpdateDeleteDatatableMocks();
JsonCommand command = createJsonCommand("{}", APP_TABLE_ID);
underTest.updateDatatableEntryOneToMany(DATATABLE_NAME, APP_TABLE_ID, DATATABLE_ID, command);
verifyBusinessEvent(APP_TABLE_ID);
}
@Test
void shouldNotifyBusinessEventAfterDeletingDatatableEntry() {
setupUpdateDeleteDatatableMocks();
JsonCommand command = createJsonCommand("{}", APP_TABLE_ID);
underTest.deleteDatatableEntry(DATATABLE_NAME, APP_TABLE_ID, DATATABLE_ID, command);
verifyBusinessEvent(APP_TABLE_ID);
}
private void setupThreadLocalContext() {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.parse("2024-01-16"),
BusinessDateType.COB_DATE, LocalDate.parse("2024-01-15"))));
SqlRowSet sqlRS = Mockito.mock(SqlRowSet.class);
SqlRowSet sqlRSData = Mockito.mock(SqlRowSet.class);
doNothing().when(sqlValidator).validate(anyString());
when(jdbcTemplate.queryForRowSet(anyString(), anyString())).thenReturn(sqlRS);
when(jdbcTemplate.queryForRowSet(anyString())).thenReturn(sqlRSData);
when(sqlRS.next()).thenReturn(true).thenReturn(false);
when(sqlRS.getString("application_table_name")).thenReturn("m_loan");
when(sqlRSData.next()).thenReturn(true).thenReturn(false);
when(sqlRSData.getObject(anyString())).thenReturn(1L);
Map<BusinessDateType, LocalDate> businessDates = Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.parse("2024-01-16"),
BusinessDateType.COB_DATE, LocalDate.parse("2024-01-15"));
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(businessDates));
}
private void setupSecurityContext() {
AppUser currentUser = Mockito.mock(AppUser.class);
Office office = Mockito.mock(Office.class);
when(context.authenticatedUser()).thenReturn(currentUser);
@@ -143,61 +178,57 @@ public class DatatableBusinessEventTest {
when(office.getHierarchy()).thenReturn(".");
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
private void setupEntityTable() {
EntityTables entityTable = Mockito.mock(EntityTables.class);
when(entityTable.getForeignKeyColumnNameOnDatatable()).thenReturn("loan_id");
when(datatableUtil.queryForApplicationEntity(anyString())).thenReturn(entityTable);
}
@Test
public void businessEventCreateNewDatatableEntryTest() {
private void setupCommandProcessingResult() {
CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
when(commandProcessingResult.getOfficeId()).thenReturn(1L);
when(datatableUtil.checkMainResourceExistsWithinScope(any(EntityTables.class), anyLong())).thenReturn(commandProcessingResult);
}
private void setupCreateDatatableEntryMocks() {
List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
when(genericDataService.fillResultsetColumnHeaders(anyString())).thenReturn(columnHeaders);
when(jdbcTemplate.update(any(PreparedStatementCreator.class), any(KeyHolder.class))).thenReturn(1);
underTest.createNewDatatableEntry(DATATABLE_NAME, 1L, createJsonCommand("{}", 1L));
GeneratedKeyHolder keyHolder = Mockito.mock(GeneratedKeyHolder.class);
when(keyHolder.getKeys()).thenReturn(Map.of("id", 1L));
when(sqlGenerator.fetchPK(any())).thenReturn(1L);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1);
verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L);
when(fromJsonHelper.extractDataMap(any(), anyString())).thenReturn(new HashMap<>());
}
@Test
public void businessEventUpdateDatatableEntryTest() {
List<Object> values = new ArrayList<>();
values.add(1L);
List<ResultsetRowData> result = new ArrayList<>();
result.add(ResultsetRowData.create(values));
when(genericDataService.fillResultsetRowData(anyString(), any())).thenReturn(result);
private void setupUpdateDeleteDatatableMocks() {
List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
List<Object> rowValues = new ArrayList<>();
rowValues.add(1L);
// ResultsetColumnHeaderData resultSet = Mockito.mock(ResultsetColumnHeaderData.class);
// when(searchUtil.getFiltered(columnHeaders,
// ResultsetColumnHeaderData::getIsColumnPrimaryKey)).thenReturn(resultSet);
List<ResultsetRowData> rows = new ArrayList<>();
rows.add(ResultsetRowData.create(rowValues));
underTest.updateDatatableEntryOneToMany(DATATABLE_NAME, 1L, 1L, createJsonCommand("{}", 1L));
GenericResultsetData resultData = new GenericResultsetData(columnHeaders, rows);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1);
verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L);
Mockito.doReturn(resultData).when(datatableUtil).retrieveDataTableGenericResultSet(any(EntityTables.class), eq(DATATABLE_NAME),
eq(APP_TABLE_ID), isNull(), eq(DATATABLE_ID));
when(genericDataService.fillResultsetRowData(anyString(), any())).thenReturn(rows);
}
@Test
public void businessEventDeleteDatatableEntryTest() {
private void verifyBusinessEvent(Long expectedEntityId) {
ArgumentCaptor<BusinessEvent<?>> eventCaptor = ArgumentCaptor.forClass(BusinessEvent.class);
verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(eventCaptor.capture());
underTest.deleteDatatableEntry(DATATABLE_NAME, 1L, 1L, createJsonCommand("{}", 1L));
BusinessEvent<?> capturedEvent = eventCaptor.getValue();
assertInstanceOf(DatatableEntryBusinessEvent.class, capturedEvent);
DatatableEntryBusinessEvent event = (DatatableEntryBusinessEvent) capturedEvent;
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1);
verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L);
}
@NotNull
private ArgumentCaptor<BusinessEvent<?>> verifyBusinessEvents(int expectedBusinessEvents) {
@SuppressWarnings("unchecked")
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = ArgumentCaptor.forClass(BusinessEvent.class);
verify(businessEventNotifierService, times(expectedBusinessEvents)).notifyPostBusinessEvent(businessEventArgumentCaptor.capture());
return businessEventArgumentCaptor;
}
private void verifyDatatableBusinessEvent(ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor, int index, Long entityId) {
assertTrue(businessEventArgumentCaptor.getAllValues().get(index) instanceof DatatableEntryBusinessEvent);
assertEquals(DATATABLE_NAME, ((DatatableEntryBusinessEvent) businessEventArgumentCaptor.getAllValues().get(index))
.getDatatableEntryDetails().getDatatableName());
assertEquals(entityId, ((DatatableEntryBusinessEvent) businessEventArgumentCaptor.getAllValues().get(index)).getAggregateRootId());
assertEquals(DATATABLE_NAME, event.getDatatableEntryDetails().getDatatableName());
assertEquals(expectedEntityId, event.getAggregateRootId());
}
private JsonCommand createJsonCommand(final String jsonCommand, final Long resourceId) {
@@ -54,7 +54,7 @@ import org.springframework.test.context.ContextConfiguration;
@SpringBootTest
@ContextConfiguration(classes = TestConfiguration.class)
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
public class ReadWriteNonCoreDataServiceImplTest {
public class DatatableReadServiceImplTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@@ -69,7 +69,7 @@ public class ReadWriteNonCoreDataServiceImplTest {
private DatabaseSpecificSQLGenerator sqlGenerator;
@Autowired
private ReadWriteNonCoreDataService underTest;
private DatatableReadService underTest;
@Test
public void testSqlInjectionCaughtQueryDataTable() {