FINERACT-1724: Limiting query result for search API to prevent OOMs and support for loanId parameter for retrieving loan reschedule requests

This commit is contained in:
Arnold Galovics
2023-05-10 21:47:17 +02:00
committed by Arnold Gálovics
parent 0f1559cc7a
commit 5b6f334379
4 changed files with 99 additions and 116 deletions
@@ -198,7 +198,8 @@ public class RescheduleLoansApiResource {
@Operation(summary = "Retrieve all reschedule requests", description = "Retrieve all reschedule requests.") @Operation(summary = "Retrieve all reschedule requests", description = "Retrieve all reschedule requests.")
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RescheduleLoansApiResourceSwagger.GetLoanRescheduleRequestResponse.class)))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RescheduleLoansApiResourceSwagger.GetLoanRescheduleRequestResponse.class)))) })
public String retrieveAllRescheduleRequest(@Context final UriInfo uriInfo, @QueryParam("command") final String command) { public String retrieveAllRescheduleRequest(@Context final UriInfo uriInfo, @QueryParam("command") final String command,
@QueryParam("loanId") Long loanId) {
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME); this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME);
@@ -209,28 +210,8 @@ public class RescheduleLoansApiResource {
RescheduleLoansApiConstants.rejectCommandParamName); RescheduleLoansApiConstants.rejectCommandParamName);
} }
final List<LoanRescheduleRequestData> loanRescheduleRequestsData = this.loanRescheduleRequestReadPlatformService final List<LoanRescheduleRequestData> loanRescheduleRequestsData = this.loanRescheduleRequestReadPlatformService
.retrieveAllRescheduleRequests(command); .retrieveAllRescheduleRequests(command, loanId);
return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleRequestsData); return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleRequestsData);
} }
/*
* @GET
*
* @Path("{scheduleId}")
*
* @Consumes({ MediaType.APPLICATION_JSON })
*
* @Produces({ MediaType.APPLICATION_JSON }) public String retrieveTemplate(@Context final UriInfo uriInfo) {
*
* this.platformSecurityContext.authenticatedUser().
* validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME); final ApiRequestJsonSerializationSettings
* settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
*
* LoanRescheduleRequestData loanRescheduleReasons = null; loanRescheduleReasons =
* this.loanRescheduleRequestReadPlatformService .retrieveAllRescheduleReasons(RescheduleLoansApiConstants.
* LOAN_RESCHEDULE_REASON);
*
* return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleReasons); }
*/
} }
@@ -67,5 +67,5 @@ public interface LoanRescheduleRequestReadPlatformService {
* pending for approval reject - give all rejected requests * pending for approval reject - give all rejected requests
* @return list of LoanRescheduleRequestData objects * @return list of LoanRescheduleRequestData objects
**/ **/
List<LoanRescheduleRequestData> retrieveAllRescheduleRequests(String command); List<LoanRescheduleRequestData> retrieveAllRescheduleRequests(String command, Long loanId);
} }
@@ -18,6 +18,8 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service; package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -271,19 +273,32 @@ public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanResched
} }
@Override @Override
public List<LoanRescheduleRequestData> retrieveAllRescheduleRequests(String command) { public List<LoanRescheduleRequestData> retrieveAllRescheduleRequests(String command, Long loanId) {
LoanRescheduleRequestRowMapperForBulkApproval loanRescheduleRequestRowMapperForBulkApproval = new LoanRescheduleRequestRowMapperForBulkApproval(); LoanRescheduleRequestRowMapperForBulkApproval loanRescheduleRequestRowMapperForBulkApproval = new LoanRescheduleRequestRowMapperForBulkApproval();
String sql = "select " + loanRescheduleRequestRowMapperForBulkApproval.schema(); String sql = "select " + loanRescheduleRequestRowMapperForBulkApproval.schema();
List<String> extraFilters = new ArrayList<>();
List<Object> extraParams = new ArrayList<>();
if (!StringUtils.isEmpty(command) && !command.equalsIgnoreCase(RescheduleLoansApiConstants.allCommandParamName)) { if (!StringUtils.isEmpty(command) && !command.equalsIgnoreCase(RescheduleLoansApiConstants.allCommandParamName)) {
sql = sql + " where lrr.status_enum = ? "; int statusParam = 100;
Integer statusParam = 100;
if (command.equalsIgnoreCase(RescheduleLoansApiConstants.approveCommandParamName)) { if (command.equalsIgnoreCase(RescheduleLoansApiConstants.approveCommandParamName)) {
statusParam = 200; statusParam = 200;
} else if (command.equalsIgnoreCase(RescheduleLoansApiConstants.rejectCommandParamName)) { } else if (command.equalsIgnoreCase(RescheduleLoansApiConstants.rejectCommandParamName)) {
statusParam = 300; statusParam = 300;
} }
return this.jdbcTemplate.query(sql, loanRescheduleRequestRowMapperForBulkApproval, statusParam); // NOSONAR
extraFilters.add("lrr.status_enum = ?");
extraParams.add(statusParam);
} }
return this.jdbcTemplate.query(sql, loanRescheduleRequestRowMapperForBulkApproval); // NOSONAR
if (loanId != null) {
extraFilters.add("loan.id = ?");
extraParams.add(loanId);
}
if (isNotEmpty(extraFilters)) {
sql += " where " + String.join(" AND ", extraFilters);
}
return jdbcTemplate.query(sql, loanRescheduleRequestRowMapperForBulkApproval, extraParams.toArray()); // NOSONAR
} }
} }
@@ -23,9 +23,11 @@ import java.math.RoundingMode;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection; import java.util.Collection;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.office.data.OfficeData; import org.apache.fineract.organisation.office.data.OfficeData;
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
@@ -42,33 +44,24 @@ import org.apache.fineract.portfolio.search.data.AdHocSearchQueryData;
import org.apache.fineract.portfolio.search.data.SearchConditions; import org.apache.fineract.portfolio.search.data.SearchConditions;
import org.apache.fineract.portfolio.search.data.SearchData; import org.apache.fineract.portfolio.search.data.SearchData;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor
public class SearchReadPlatformServiceImpl implements SearchReadPlatformService { public class SearchReadPlatformServiceImpl implements SearchReadPlatformService {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final LoanProductReadPlatformService loanProductReadPlatformService; private final LoanProductReadPlatformService loanProductReadPlatformService;
private final OfficeReadPlatformService officeReadPlatformService; private final OfficeReadPlatformService officeReadPlatformService;
private final DatabaseSpecificSQLGenerator sqlGenerator;
@Autowired
public SearchReadPlatformServiceImpl(final PlatformSecurityContext context, final NamedParameterJdbcTemplate namedParameterJdbcTemplate,
final LoanProductReadPlatformService loanProductReadPlatformService,
final OfficeReadPlatformService officeReadPlatformService) {
this.context = context;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
this.loanProductReadPlatformService = loanProductReadPlatformService;
this.officeReadPlatformService = officeReadPlatformService;
}
@Override @Override
public Collection<SearchData> retriveMatchingData(final SearchConditions searchConditions) { public Collection<SearchData> retriveMatchingData(final SearchConditions searchConditions) {
final AppUser currentUser = this.context.authenticatedUser(); final AppUser currentUser = context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy(); final String hierarchy = currentUser.getOffice().getHierarchy();
final SearchMapper rm = new SearchMapper(); final SearchMapper rm = new SearchMapper();
@@ -80,11 +73,9 @@ public class SearchReadPlatformServiceImpl implements SearchReadPlatformService
} else { } else {
params.addValue("search", "%" + searchConditions.getSearchQuery() + "%"); params.addValue("search", "%" + searchConditions.getSearchQuery() + "%");
} }
return this.namedParameterJdbcTemplate.query(rm.searchSchema(searchConditions), params, rm); return namedParameterJdbcTemplate.query(searchSchema(searchConditions), params, rm);
} }
private static final class SearchMapper implements RowMapper<SearchData> {
public String searchSchema(final SearchConditions searchConditions) { public String searchSchema(final SearchConditions searchConditions) {
final String union = " union "; final String union = " union ";
@@ -144,9 +135,13 @@ public class SearchReadPlatformServiceImpl implements SearchReadPlatformService
// remove last occurrence of "union all" string // remove last occurrence of "union all" string
sql.replace(sql.lastIndexOf(union), sql.length(), ""); sql.replace(sql.lastIndexOf(union), sql.length(), "");
// only get the first 50 rows in case of searcing
sql.append(" ").append(sqlGenerator.limit(50, 0));
return sql.toString(); return sql.toString();
} }
private static final class SearchMapper implements RowMapper<SearchData> {
@Override @Override
public SearchData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { public SearchData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long entityId = JdbcSupport.getLong(rs, "entityId"); final Long entityId = JdbcSupport.getLong(rs, "entityId");
@@ -187,10 +182,10 @@ public class SearchReadPlatformServiceImpl implements SearchReadPlatformService
@Override @Override
public AdHocSearchQueryData retrieveAdHocQueryTemplate() { public AdHocSearchQueryData retrieveAdHocQueryTemplate() {
this.context.authenticatedUser(); context.authenticatedUser();
final Collection<LoanProductData> loanProducts = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(); final Collection<LoanProductData> loanProducts = loanProductReadPlatformService.retrieveAllLoanProductsForLookup();
final Collection<OfficeData> offices = this.officeReadPlatformService.retrieveAllOfficesForDropdown(); final Collection<OfficeData> offices = officeReadPlatformService.retrieveAllOfficesForDropdown();
return AdHocSearchQueryData.template(loanProducts, offices); return AdHocSearchQueryData.template(loanProducts, offices);
} }
@@ -198,12 +193,12 @@ public class SearchReadPlatformServiceImpl implements SearchReadPlatformService
@Override @Override
public Collection<AdHocSearchQueryData> retrieveAdHocQueryMatchingData(final AdHocQuerySearchConditions searchConditions) { public Collection<AdHocSearchQueryData> retrieveAdHocQueryMatchingData(final AdHocQuerySearchConditions searchConditions) {
this.context.authenticatedUser(); context.authenticatedUser();
final AdHocQuerySearchMapper rm = new AdHocQuerySearchMapper(); final AdHocQuerySearchMapper rm = new AdHocQuerySearchMapper();
final MapSqlParameterSource params = new MapSqlParameterSource(); final MapSqlParameterSource params = new MapSqlParameterSource();
return this.namedParameterJdbcTemplate.query(rm.schema(searchConditions, params), params, rm); return namedParameterJdbcTemplate.query(rm.schema(searchConditions, params), params, rm);
} }
private static final class AdHocQuerySearchMapper implements RowMapper<AdHocSearchQueryData> { private static final class AdHocQuerySearchMapper implements RowMapper<AdHocSearchQueryData> {
@@ -272,15 +267,11 @@ public class SearchReadPlatformServiceImpl implements SearchReadPlatformService
if (searchConditions.getIncludeOutStandingAmountPercentage()) { if (searchConditions.getIncludeOutStandingAmountPercentage()) {
if (searchConditions.getOutStandingAmountPercentageCondition().equals("between")) { if (searchConditions.getOutStandingAmountPercentageCondition().equals("between")) {
checkAndUpdateWhereClause(sql); checkAndUpdateWhereClause(sql);
// params.addValue("outStandingAmountPercentageCondition",
// searchConditions.getOutStandingAmountPercentageCondition());
params.addValue("minOutStandingAmountPercentage", searchConditions.getMinOutStandingAmountPercentage()); params.addValue("minOutStandingAmountPercentage", searchConditions.getMinOutStandingAmountPercentage());
params.addValue("maxOutStandingAmountPercentage", searchConditions.getMaxOutStandingAmountPercentage()); params.addValue("maxOutStandingAmountPercentage", searchConditions.getMaxOutStandingAmountPercentage());
sql.append(" ( a.percentOut between :minOutStandingAmountPercentage and :maxOutStandingAmountPercentage ) "); sql.append(" ( a.percentOut between :minOutStandingAmountPercentage and :maxOutStandingAmountPercentage ) ");
} else { } else {
checkAndUpdateWhereClause(sql); checkAndUpdateWhereClause(sql);
// params.addValue("outStandingAmountPercentageCondition",
// searchConditions.getOutStandingAmountPercentageCondition());
params.addValue("outStandingAmountPercentage", searchConditions.getOutStandingAmountPercentage()); params.addValue("outStandingAmountPercentage", searchConditions.getOutStandingAmountPercentage());
sql.append(" a.percentOut ").append(searchConditions.getOutStandingAmountPercentageCondition()) sql.append(" a.percentOut ").append(searchConditions.getOutStandingAmountPercentageCondition())
.append(" :outStandingAmountPercentage "); .append(" :outStandingAmountPercentage ");
@@ -290,15 +281,11 @@ public class SearchReadPlatformServiceImpl implements SearchReadPlatformService
if (searchConditions.getIncludeOutstandingAmount()) { if (searchConditions.getIncludeOutstandingAmount()) {
if (searchConditions.getOutstandingAmountCondition().equals("between")) { if (searchConditions.getOutstandingAmountCondition().equals("between")) {
checkAndUpdateWhereClause(sql); checkAndUpdateWhereClause(sql);
// params.addValue("outstandingAmountCondition",
// searchConditions.getOutstandingAmountCondition());
params.addValue("minOutstandingAmount", searchConditions.getMinOutstandingAmount()); params.addValue("minOutstandingAmount", searchConditions.getMinOutstandingAmount());
params.addValue("maxOutstandingAmount", searchConditions.getMaxOutstandingAmount()); params.addValue("maxOutstandingAmount", searchConditions.getMaxOutstandingAmount());
sql.append(" ( a.outstandingAmt between :minOutstandingAmount and :maxOutstandingAmount ) "); sql.append(" ( a.outstandingAmt between :minOutstandingAmount and :maxOutstandingAmount ) ");
} else { } else {
checkAndUpdateWhereClause(sql); checkAndUpdateWhereClause(sql);
// params.addValue("outstandingAmountCondition",
// searchConditions.getOutstandingAmountCondition());
params.addValue("outstandingAmount", searchConditions.getOutstandingAmount()); params.addValue("outstandingAmount", searchConditions.getOutstandingAmount());
sql.append(" a.outstandingAmt ").append(searchConditions.getOutstandingAmountCondition()) sql.append(" a.outstandingAmt ").append(searchConditions.getOutstandingAmountCondition())
.append(" :outstandingAmount "); .append(" :outstandingAmount ");