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; 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.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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; import org.springframework.security.access.prepost.PreAuthorize;
public interface ReadWriteNonCoreDataService { public interface DatatableWriteService {
List<DatatableData> retrieveDatatableNames(String appTable);
DatatableData retrieveDatatable(String datatable);
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')") @PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
void registerDatatable(JsonCommand command); void registerDatatable(JsonCommand command);
@@ -50,8 +36,6 @@ public interface ReadWriteNonCoreDataService {
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'DEREGISTER_DATATABLE')") @PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'DEREGISTER_DATATABLE')")
void deregisterDatatable(String datatable); void deregisterDatatable(String datatable);
GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
CommandProcessingResult createDatatable(JsonCommand command); CommandProcessingResult createDatatable(JsonCommand command);
void updateDatatable(String datatableName, JsonCommand command); void updateDatatable(String datatableName, JsonCommand command);
@@ -62,8 +46,6 @@ public interface ReadWriteNonCoreDataService {
CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, String json); 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 updateDatatableEntryOneToOne(String datatable, Long appTableId, JsonCommand command);
CommandProcessingResult updateDatatableEntryOneToMany(String datatable, Long appTableId, Long datatableId, 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); 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.core.service.PagedLocalRequest;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData; import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; 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.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData; import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -72,7 +73,8 @@ public class DatatablesApiResource {
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final GenericDataService genericDataService; private final GenericDataService genericDataService;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService; private final DatatableReadService datatableReadService;
private final DatatableWriteService datatableWriteService;
private final ToApiJsonSerializer<GenericResultsetData> toApiJsonSerializer; private final ToApiJsonSerializer<GenericResultsetData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@@ -87,7 +89,7 @@ public class DatatablesApiResource {
public String getDatatables(@QueryParam("apptable") @Parameter(description = "apptable") final String apptable, public String getDatatables(@QueryParam("apptable") @Parameter(description = "apptable") final String apptable,
@Context final UriInfo uriInfo) { @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); return this.toApiJsonSerializer.serialize(result);
} }
@@ -184,7 +186,7 @@ public class DatatablesApiResource {
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DatatablesApiResourceSwagger.PutDataTablesResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DatatablesApiResourceSwagger.PutDataTablesResponse.class))) })
public String deregisterDatatable(@PathParam("datatable") @Parameter(description = "datatable") final String datatable) { 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(); final CommandProcessingResult result = new CommandProcessingResultBuilder().withResourceIdAsString(datatable).build();
return this.toApiJsonSerializer.serialize(result); return this.toApiJsonSerializer.serialize(result);
} }
@@ -199,7 +201,7 @@ public class DatatablesApiResource {
public String getDatatable(@PathParam("datatable") @Parameter(description = "datatable") final String datatable, public String getDatatable(@PathParam("datatable") @Parameter(description = "datatable") final String datatable,
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
final DatatableData result = this.readWriteNonCoreDataService.retrieveDatatable(datatable); final DatatableData result = this.datatableReadService.retrieveDatatable(datatable);
return this.toApiJsonSerializer.serialize(result); return this.toApiJsonSerializer.serialize(result);
} }
@@ -216,8 +218,7 @@ public class DatatablesApiResource {
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasDatatableReadPermission(datatable); this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
final List<JsonObject> result = this.readWriteNonCoreDataService.queryDataTable(datatable, columnFilter, valueFilter, final List<JsonObject> result = this.datatableReadService.queryDataTable(datatable, columnFilter, valueFilter, resultColumns);
resultColumns);
return this.toApiJsonSerializer.serialize(result); return this.toApiJsonSerializer.serialize(result);
} }
@@ -231,7 +232,7 @@ public class DatatablesApiResource {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = List.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = List.class))) })
public String advancedQuery(@PathParam("datatable") @Parameter(description = "datatable") final String datatable, public String advancedQuery(@PathParam("datatable") @Parameter(description = "datatable") final String datatable,
PagedLocalRequest<AdvancedQueryData> queryRequest, @Context final UriInfo uriInfo) { 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); return this.toApiJsonSerializer.serialize(result);
} }
@@ -254,8 +255,8 @@ public class DatatablesApiResource {
this.context.authenticatedUser().validateHasDatatableReadPermission(datatable); this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
final GenericResultsetData results = this.readWriteNonCoreDataService.retrieveDataTableGenericResultSet(datatable, apptableId, final GenericResultsetData results = this.datatableReadService.retrieveDataTableGenericResultSet(datatable, apptableId, order,
order, null); null);
String json = ""; String json = "";
final boolean genericResultSet = ApiParameterHelper.genericResultSet(uriInfo.getQueryParameters()); final boolean genericResultSet = ApiParameterHelper.genericResultSet(uriInfo.getQueryParameters());
@@ -280,8 +281,8 @@ public class DatatablesApiResource {
this.context.authenticatedUser().validateHasDatatableReadPermission(datatable); this.context.authenticatedUser().validateHasDatatableReadPermission(datatable);
final GenericResultsetData results = this.readWriteNonCoreDataService.retrieveDataTableGenericResultSet(datatable, apptableId, final GenericResultsetData results = this.datatableReadService.retrieveDataTableGenericResultSet(datatable, apptableId, order,
order, datatableId); datatableId);
String json = ""; String json = "";
if (genericResultSet) { if (genericResultSet) {
@@ -22,7 +22,7 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -30,12 +30,12 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CreateDatatableCommandHandler implements NewCommandSourceHandler { public class CreateDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
return datatableWriteService.createDatatable(command);
return writePlatformService.createDatatable(command);
} }
} }
@@ -22,7 +22,7 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -30,12 +30,12 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CreateDatatableEntryCommandHandler implements NewCommandSourceHandler { public class CreateDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
return datatableWriteService.createNewDatatableEntry(command.getEntityName(), command.entityId(), command);
return writePlatformService.createNewDatatableEntry(command.getEntityName(), command.entityId(), command);
} }
} }
@@ -18,30 +18,27 @@
*/ */
package org.apache.fineract.infrastructure.dataqueries.handler; package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class DeleteDatatableCommandHandler implements NewCommandSourceHandler { public class DeleteDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Autowired
public DeleteDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
final String datatableName = command.getUrl().replaceAll("/datatables/", ""); final String datatableName = command.getUrl().replaceAll("/datatables/", "");
this.writePlatformService.deleteDatatable(datatableName); datatableWriteService.deleteDatatable(datatableName);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build(); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
} }
} }
@@ -18,27 +18,24 @@
*/ */
package org.apache.fineract.infrastructure.dataqueries.handler; package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class DeleteOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler { public class DeleteOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Autowired
public DeleteOneToManyDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { 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; package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class DeleteOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler { public class DeleteOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Autowired
public DeleteOneToOneDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { 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; package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService; import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
import org.springframework.beans.factory.annotation.Autowired; import org.apache.fineract.infrastructure.dataqueries.service.DatatableWriteService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class RegisterDatatableCommandHandler implements NewCommandSourceHandler { public class RegisterDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
private final DatatableReadService datatableReadService;
@Autowired
public RegisterDatatableCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
datatableWriteService.registerDatatable(command);
this.writePlatformService.registerDatatable(command); return new CommandProcessingResultBuilder().withResourceIdAsString(datatableReadService.getDataTableName(command.getUrl())).build();
return new CommandProcessingResultBuilder().withResourceIdAsString(this.writePlatformService.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.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -31,16 +31,16 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class UpdateDatatableCommandHandler implements NewCommandSourceHandler { public class UpdateDatatableCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
final String datatableName = command.getUrl().replaceAll("/datatables/", ""); 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(); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
} }
} }
@@ -18,30 +18,26 @@
*/ */
package org.apache.fineract.infrastructure.dataqueries.handler; package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class UpdateOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler { public class UpdateOneToManyDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Autowired
public UpdateOneToManyDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
return this.datatableWriteService.updateDatatableEntryOneToMany(command.getEntityName(), command.entityId(), command.subentityId(),
return this.writePlatformService.updateDatatableEntryOneToMany(command.getEntityName(), command.entityId(), command.subentityId(),
command); command);
} }
} }
@@ -18,30 +18,25 @@
*/ */
package org.apache.fineract.infrastructure.dataqueries.handler; package org.apache.fineract.infrastructure.dataqueries.handler;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class UpdateOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler { public class UpdateOneToOneDatatableEntryCommandHandler implements NewCommandSourceHandler {
private final ReadWriteNonCoreDataService writePlatformService; private final DatatableWriteService datatableWriteService;
@Autowired
public UpdateOneToOneDatatableEntryCommandHandler(final ReadWriteNonCoreDataService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
final CommandProcessingResult commandProcessingResult = this.datatableWriteService
final CommandProcessingResult commandProcessingResult = this.writePlatformService
.updateDatatableEntryOneToOne(command.getEntityName(), command.entityId(), command); .updateDatatableEntryOneToOne(command.getEntityName(), command.entityId(), command);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -55,4 +50,5 @@ public class UpdateOneToOneDatatableEntryCommandHandler implements NewCommandSou
.with(commandProcessingResult.getChanges()) // .with(commandProcessingResult.getChanges()) //
.build(); .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.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.SearchParameters; 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.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.JdbcJavaType;
import org.apache.fineract.infrastructure.core.service.database.SqlOperator; import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableCheckStatusData; 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.loanproduct.service.LoanProductReadPlatformService;
import org.apache.fineract.portfolio.savings.data.SavingsProductData; import org.apache.fineract.portfolio.savings.data.SavingsProductData;
import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; 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.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -55,33 +53,28 @@ import org.springframework.stereotype.Service;
public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatatableChecksReadService { public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatatableChecksReadService {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
protected final DatabaseTypeResolver databaseTypeResolver;
private final DatabaseSpecificSQLGenerator sqlGenerator; private final DatabaseSpecificSQLGenerator sqlGenerator;
private final RegisterDataTableMapper registerDataTableMapper; private final RegisterDataTableMapper registerDataTableMapper;
private final EntityDataTableChecksMapper entityDataTableChecksMapper; private final EntityDataTableChecksMapper entityDataTableChecksMapper;
private final EntityDatatableChecksRepository entityDatatableChecksRepository; private final EntityDatatableChecksRepository entityDatatableChecksRepository;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService; private final DatatableReadService datatableReadService;
private final LoanProductReadPlatformService loanProductReadPlatformService; private final LoanProductReadPlatformService loanProductReadPlatformService;
private final SavingsProductReadPlatformService savingsProductReadPlatformService; private final SavingsProductReadPlatformService savingsProductReadPlatformService;
private final PaginationHelper paginationHelper; private final PaginationHelper paginationHelper;
@Autowired public EntityDatatableChecksReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final DatabaseSpecificSQLGenerator sqlGenerator,
public EntityDatatableChecksReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseTypeResolver databaseTypeResolver, final EntityDatatableChecksRepository entityDatatableChecksRepository, final DatatableReadService datatableReadService,
DatabaseSpecificSQLGenerator sqlGenerator, final LoanProductReadPlatformService loanProductReadPlatformService, final LoanProductReadPlatformService loanProductReadPlatformService,
final SavingsProductReadPlatformService savingsProductReadPlatformService, final SavingsProductReadPlatformService savingsProductReadPlatformService, final PaginationHelper paginationHelper) {
final EntityDatatableChecksRepository entityDatatableChecksRepository,
final ReadWriteNonCoreDataService readWriteNonCoreDataService, PaginationHelper paginationHelper) {
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
this.databaseTypeResolver = databaseTypeResolver;
this.sqlGenerator = sqlGenerator; this.sqlGenerator = sqlGenerator;
this.registerDataTableMapper = new RegisterDataTableMapper(); this.entityDatatableChecksRepository = entityDatatableChecksRepository;
this.entityDataTableChecksMapper = new EntityDataTableChecksMapper(); this.datatableReadService = datatableReadService;
this.loanProductReadPlatformService = loanProductReadPlatformService; this.loanProductReadPlatformService = loanProductReadPlatformService;
this.savingsProductReadPlatformService = savingsProductReadPlatformService; this.savingsProductReadPlatformService = savingsProductReadPlatformService;
this.entityDatatableChecksRepository = entityDatatableChecksRepository;
this.readWriteNonCoreDataService = readWriteNonCoreDataService;
this.paginationHelper = paginationHelper; this.paginationHelper = paginationHelper;
this.registerDataTableMapper = new RegisterDataTableMapper();
this.entityDataTableChecksMapper = new EntityDataTableChecksMapper();
} }
@Override @Override
@@ -134,7 +127,7 @@ public class EntityDatatableChecksReadPlatformServiceImpl implements EntityDatat
if (tableRequiredBeforeAction != null && tableRequiredBeforeAction.size() > 0) { if (tableRequiredBeforeAction != null && tableRequiredBeforeAction.size() > 0) {
List<DatatableData> ret = new ArrayList<>(); List<DatatableData> ret = new ArrayList<>();
for (EntityDatatableChecks t : tableRequiredBeforeAction) { for (EntityDatatableChecks t : tableRequiredBeforeAction) {
ret.add(this.readWriteNonCoreDataService.retrieveDatatable(t.getDatatableName())); ret.add(this.datatableReadService.retrieveDatatable(t.getDatatableName()));
} }
return ret; return ret;
} }
@@ -60,7 +60,8 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final EntityDatatableChecksDataValidator fromApiJsonDeserializer; private final EntityDatatableChecksDataValidator fromApiJsonDeserializer;
private final EntityDatatableChecksRepository entityDatatableChecksRepository; private final EntityDatatableChecksRepository entityDatatableChecksRepository;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService; private final DatatableReadService datatableReadService;
private final DatatableWriteService datatableWriteService;
private final LoanProductReadPlatformService loanProductReadPlatformService; private final LoanProductReadPlatformService loanProductReadPlatformService;
private final SavingsProductReadPlatformService savingsProductReadPlatformService; private final SavingsProductReadPlatformService savingsProductReadPlatformService;
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@@ -77,7 +78,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
// check if the datatable is linked to the entity // check if the datatable is linked to the entity
String datatableName = command.stringValueOfParameterNamed("datatableName"); String datatableName = command.stringValueOfParameterNamed("datatableName");
DatatableData datatableData = this.readWriteNonCoreDataService.retrieveDatatable(datatableName); DatatableData datatableData = this.datatableReadService.retrieveDatatable(datatableName);
if (datatableData == null) { if (datatableData == null) {
throw new DatatableNotFoundException(datatableName); throw new DatatableNotFoundException(datatableName);
@@ -154,7 +155,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
for (EntityDatatableChecks t : tableRequiredBeforeClientActivation) { for (EntityDatatableChecks t : tableRequiredBeforeClientActivation) {
final String datatableName = t.getDatatableName(); 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); log.debug("The are {} entries in the table {}", countEntries, datatableName);
if (countEntries.intValue() == 0) { if (countEntries.intValue() == 0) {
@@ -182,7 +183,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
for (EntityDatatableChecks t : tableRequiredBeforAction) { for (EntityDatatableChecks t : tableRequiredBeforAction) {
final String datatableName = t.getDatatableName(); 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); log.debug("The are {} entries in the table {}", countEntries, datatableName);
if (countEntries.intValue() == 0) { if (countEntries.intValue() == 0) {
@@ -220,7 +221,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
isMakerCheckerEnabled = true; isMakerCheckerEnabled = true;
} }
try { try {
this.readWriteNonCoreDataService.createNewDatatableEntry(datatableName, entityId, datatableData.toString()); datatableWriteService.createNewDatatableEntry(datatableName, entityId, datatableData.toString());
} catch (PlatformApiDataValidationException e) { } catch (PlatformApiDataValidationException e) {
for (ApiParameterError error : e.getErrors()) { for (ApiParameterError error : e.getErrors()) {
error.setParameterName("datatables." + datatableName + "." + error.getParameterName()); 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.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator; import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.service.DatatableKeywordGenerator; 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.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.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator; 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.portfolio.search.service.SearchUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -45,17 +47,27 @@ public class DataQueriesAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ReadWriteNonCoreDataService readWriteNonCoreDataService(final JdbcTemplate jdbcTemplate, public DatatableReadService datatableReadService(final JdbcTemplate jdbcTemplate, final DatabaseSpecificSQLGenerator sqlGenerator,
final DatabaseTypeResolver databaseTypeResolver, final DatabaseSpecificSQLGenerator sqlGenerator, final PlatformSecurityContext context, final GenericDataService genericDataService, final DataTableValidator dataTableValidator,
final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService, final SqlValidator sqlValidator, final SearchUtil searchUtil, final DatatableUtil datatableUtil) {
final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer, return new DatatableReadServiceImpl(jdbcTemplate, sqlGenerator, context, genericDataService, dataTableValidator, sqlValidator,
final ConfigurationDomainService configurationDomainService, final CodeReadPlatformService codeReadPlatformService, searchUtil, datatableUtil);
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);
} }
@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.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData; 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.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator; import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview; import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
@@ -46,7 +46,7 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final SqlValidator sqlValidator; private final SqlValidator sqlValidator;
private final GenericDataService genericDataService; private final GenericDataService genericDataService;
private final ReadWriteNonCoreDataService readWriteNonCoreDataService; private final DatatableReadService datatableReadService;
@Override @Override
public List<SurveyDataTableData> retrieveAllSurveys() { public List<SurveyDataTableData> retrieveAllSurveys() {
@@ -183,7 +183,7 @@ public class ReadSurveyServiceImpl implements ReadSurveyService {
@Override @Override
public GenericResultsetData retrieveSurveyEntry(String surveyName, Long clientId, Long entryId) { 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.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -35,16 +36,16 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class WriteSurveyServiceImpl implements WriteSurveyService { public class WriteSurveyServiceImpl implements WriteSurveyService {
private final ReadWriteNonCoreDataService readWriteNonCoreDataService; private final DatatableReadService datatableReadService;
private final DatatableWriteService datatableWriteService;
private final DatabaseSpecificSQLGenerator sqlGenerator; private final DatabaseSpecificSQLGenerator sqlGenerator;
@Override @Override
@Transactional @Transactional
public CommandProcessingResult registerSurvey(JsonCommand command) { public CommandProcessingResult registerSurvey(JsonCommand command) {
final String dataTableName = datatableReadService.getDataTableName(command.getUrl());
final String dataTableName = this.readWriteNonCoreDataService.getDataTableName(command.getUrl());
final String permissionSql = this.getPermissionSql(dataTableName); final String permissionSql = this.getPermissionSql(dataTableName);
this.readWriteNonCoreDataService.registerDatatable(command, permissionSql); datatableWriteService.registerDatatable(command, permissionSql);
return CommandProcessingResult.commandOnlyResult(command.commandId()); return CommandProcessingResult.commandOnlyResult(command.commandId());
@@ -73,8 +74,7 @@ public class WriteSurveyServiceImpl implements WriteSurveyService {
@Transactional @Transactional
@Override @Override
public CommandProcessingResult fullFillSurvey(final String dataTableName, final Long appTableId, final JsonCommand command) { public CommandProcessingResult fullFillSurvey(final String dataTableName, final Long appTableId, final JsonCommand command) {
return datatableWriteService.createPPIEntry(dataTableName, appTableId, command);
return readWriteNonCoreDataService.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.DataTableValidator;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables; import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData; 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.GenericDataService;
import org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
@@ -66,7 +66,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final GenericDataService genericDataService; private final GenericDataService genericDataService;
private final DatabaseSpecificSQLGenerator sqlGenerator; private final DatabaseSpecificSQLGenerator sqlGenerator;
private final ReadWriteNonCoreDataService datatableService; private final DatatableReadService datatableReadService;
private final DataTableValidator dataTableValidator; private final DataTableValidator dataTableValidator;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final SearchUtil searchUtil; private final SearchUtil searchUtil;
@@ -239,7 +239,7 @@ public class SavingsAccountTransactionsSearchServiceImpl implements SavingsAccou
ArrayList<Object> dataParams = new ArrayList<>(); ArrayList<Object> dataParams = new ArrayList<>();
for (int i = 0; i < datatableQueries.size(); i++) { for (int i = 0; i < datatableQueries.size(); i++) {
TableQueryData tableQuery = datatableQueries.get(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, tableQuery.getQuery(), selectColumns, dataSelect, dataFrom, dataWhere, dataParams, alias, ("d" + i), dateFormat,
dateTimeFormat, locale); dateTimeFormat, locale);
if (added) { 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.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator; 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.EntityDatatableChecksReadService;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService; 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.entityaccess.service.FineractEntityAccessUtil;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -154,7 +154,7 @@ public class SavingsConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class) @ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context, public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator, ReadWriteNonCoreDataService datatableService, GenericDataService genericDataService, DatabaseSpecificSQLGenerator sqlGenerator, DatatableReadService datatableService,
DataTableValidator dataTableValidator, JdbcTemplate jdbcTemplate, SearchUtil searchUtil) { DataTableValidator dataTableValidator, JdbcTemplate jdbcTemplate, SearchUtil searchUtil) {
return new SavingsAccountTransactionsSearchServiceImpl(context, genericDataService, sqlGenerator, datatableService, return new SavingsAccountTransactionsSearchServiceImpl(context, genericDataService, sqlGenerator, datatableService,
dataTableValidator, jdbcTemplate, searchUtil); dataTableValidator, jdbcTemplate, searchUtil);
@@ -18,11 +18,13 @@
*/ */
package org.apache.fineract.infrastructure.dataqueries.service; package org.apache.fineract.infrastructure.dataqueries.service;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString; 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.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; 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.codes.service.CodeReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand; 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.ActionContext;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; 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.DatatableCommandFromApiJsonDeserializer;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; 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.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver; import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator; 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.dataqueries.data.ResultsetRowData;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; 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.domain.datatable.DatatableEntryBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; 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.organisation.office.domain.Office;
import org.apache.fineract.portfolio.search.service.SearchUtil; import org.apache.fineract.portfolio.search.service.SearchUtil;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; 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.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder; import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;
@ExtendWith(MockitoExtension.class) @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 @Mock
private JdbcTemplate jdbcTemplate; private JdbcTemplate jdbcTemplate;
@Mock @Mock
private GenericDataService genericDataService;
@Mock
private SearchUtil searchUtil;
@Mock
private DatabaseTypeResolver databaseTypeResolver; private DatabaseTypeResolver databaseTypeResolver;
@Mock @Mock
private DatabaseSpecificSQLGenerator sqlGenerator; private DatabaseSpecificSQLGenerator sqlGenerator;
@Mock @Mock
private FineractPlatformTenantConnection tenantConnection; private PlatformSecurityContext context;
@Mock
private DefaultSqlValidator sqlValidator;
@Mock @Mock
private FromJsonHelper fromJsonHelper; private FromJsonHelper fromJsonHelper;
@Mock @Mock
private GenericDataService genericDataService;
@Mock
private DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer; private DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer;
@Mock @Mock
private ConfigurationDomainService configurationDomainService; private ConfigurationDomainService configurationDomainService;
@@ -104,38 +104,73 @@ public class DatatableBusinessEventTest {
@Mock @Mock
private DataTableValidator dataTableValidator; private DataTableValidator dataTableValidator;
@Mock @Mock
private ColumnValidator columnValidator;
@Mock
private NamedParameterJdbcTemplate namedParameterJdbcTemplate; private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Mock @Mock
private DatatableKeywordGenerator datatableKeywordGenerator; private DatatableKeywordGenerator datatableKeywordGenerator;
@Mock
private SearchUtil searchUtil;
@Mock
private BusinessEventNotifierService businessEventNotifierService;
@Mock
private DatatableReadService datatableReadService;
@Mock
private DatatableUtil datatableUtil;
@InjectMocks @InjectMocks
private ReadWriteNonCoreDataServiceImpl underTest; private DatatableWriteServiceImpl underTest;
@Captor
private ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor;
private static String DATATABLE_NAME = "test_loan_data";
@BeforeEach @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.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.parse("2024-01-16"), Map<BusinessDateType, LocalDate> businessDates = Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.parse("2024-01-16"),
BusinessDateType.COB_DATE, LocalDate.parse("2024-01-15")))); BusinessDateType.COB_DATE, LocalDate.parse("2024-01-15"));
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(businessDates));
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);
private void setupSecurityContext() {
AppUser currentUser = Mockito.mock(AppUser.class); AppUser currentUser = Mockito.mock(AppUser.class);
Office office = Mockito.mock(Office.class); Office office = Mockito.mock(Office.class);
when(context.authenticatedUser()).thenReturn(currentUser); when(context.authenticatedUser()).thenReturn(currentUser);
@@ -143,61 +178,57 @@ public class DatatableBusinessEventTest {
when(office.getHierarchy()).thenReturn("."); when(office.getHierarchy()).thenReturn(".");
} }
@AfterEach private void setupEntityTable() {
public void tearDown() { EntityTables entityTable = Mockito.mock(EntityTables.class);
ThreadLocalContextUtil.reset(); when(entityTable.getForeignKeyColumnNameOnDatatable()).thenReturn("loan_id");
when(datatableUtil.queryForApplicationEntity(anyString())).thenReturn(entityTable);
} }
@Test private void setupCommandProcessingResult() {
public void businessEventCreateNewDatatableEntryTest() { 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); 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); when(fromJsonHelper.extractDataMap(any(), anyString())).thenReturn(new HashMap<>());
verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L);
} }
@Test private void setupUpdateDeleteDatatableMocks() {
public void businessEventUpdateDatatableEntryTest() { List<ResultsetColumnHeaderData> columnHeaders = new ArrayList<>();
List<Object> values = new ArrayList<>(); List<Object> rowValues = new ArrayList<>();
values.add(1L); rowValues.add(1L);
List<ResultsetRowData> result = new ArrayList<>();
result.add(ResultsetRowData.create(values));
when(genericDataService.fillResultsetRowData(anyString(), any())).thenReturn(result);
// ResultsetColumnHeaderData resultSet = Mockito.mock(ResultsetColumnHeaderData.class); List<ResultsetRowData> rows = new ArrayList<>();
// when(searchUtil.getFiltered(columnHeaders, rows.add(ResultsetRowData.create(rowValues));
// ResultsetColumnHeaderData::getIsColumnPrimaryKey)).thenReturn(resultSet);
underTest.updateDatatableEntryOneToMany(DATATABLE_NAME, 1L, 1L, createJsonCommand("{}", 1L)); GenericResultsetData resultData = new GenericResultsetData(columnHeaders, rows);
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1); Mockito.doReturn(resultData).when(datatableUtil).retrieveDataTableGenericResultSet(any(EntityTables.class), eq(DATATABLE_NAME),
verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L); eq(APP_TABLE_ID), isNull(), eq(DATATABLE_ID));
when(genericDataService.fillResultsetRowData(anyString(), any())).thenReturn(rows);
} }
@Test private void verifyBusinessEvent(Long expectedEntityId) {
public void businessEventDeleteDatatableEntryTest() { 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); assertEquals(DATATABLE_NAME, event.getDatatableEntryDetails().getDatatableName());
verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L); assertEquals(expectedEntityId, event.getAggregateRootId());
}
@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());
} }
private JsonCommand createJsonCommand(final String jsonCommand, final Long resourceId) { private JsonCommand createJsonCommand(final String jsonCommand, final Long resourceId) {
@@ -54,7 +54,7 @@ import org.springframework.test.context.ContextConfiguration;
@SpringBootTest @SpringBootTest
@ContextConfiguration(classes = TestConfiguration.class) @ContextConfiguration(classes = TestConfiguration.class)
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive") @SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
public class ReadWriteNonCoreDataServiceImplTest { public class DatatableReadServiceImplTest {
@Autowired @Autowired
private JdbcTemplate jdbcTemplate; private JdbcTemplate jdbcTemplate;
@@ -69,7 +69,7 @@ public class ReadWriteNonCoreDataServiceImplTest {
private DatabaseSpecificSQLGenerator sqlGenerator; private DatabaseSpecificSQLGenerator sqlGenerator;
@Autowired @Autowired
private ReadWriteNonCoreDataService underTest; private DatatableReadService underTest;
@Test @Test
public void testSqlInjectionCaughtQueryDataTable() { public void testSqlInjectionCaughtQueryDataTable() {