diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/ReportsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/ReportsApiResource.java index 990aef8ea..b4df23c79 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/ReportsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/ReportsApiResource.java @@ -51,6 +51,7 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSeria import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; import org.apache.fineract.infrastructure.dataqueries.data.ReportData; import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService; +import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; @@ -71,17 +72,19 @@ public class ReportsApiResource { private final ReadReportingService readReportingService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final ApiRequestParameterHelper apiRequestParameterHelper; + private final ReportingProcessServiceProvider reportingProcessServiceProvider; @Autowired public ReportsApiResource(final PlatformSecurityContext context, final ReadReportingService readReportingService, final ToApiJsonSerializer toApiJsonSerializer, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final ApiRequestParameterHelper apiRequestParameterHelper) { + final ApiRequestParameterHelper apiRequestParameterHelper, ReportingProcessServiceProvider reportingProcessServiceProvider) { this.context = context; this.readReportingService = readReportingService; this.toApiJsonSerializer = toApiJsonSerializer; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.apiRequestParameterHelper = apiRequestParameterHelper; + this.reportingProcessServiceProvider = reportingProcessServiceProvider; } @GET @@ -117,7 +120,8 @@ public class ReportsApiResource { final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); if (settings.isTemplate()) { - result.appendedTemplate(this.readReportingService.getAllowedParameters(), this.readReportingService.getAllowedReportTypes()); + result.appendedTemplate(this.readReportingService.getAllowedParameters(), + this.reportingProcessServiceProvider.findAllReportingTypes()); } return this.toApiJsonSerializer.serialize(settings, result, this.responseDataParameters); } @@ -135,7 +139,8 @@ public class ReportsApiResource { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); final ReportData result = new ReportData(); - result.appendedTemplate(this.readReportingService.getAllowedParameters(), this.readReportingService.getAllowedReportTypes()); + result.appendedTemplate(this.readReportingService.getAllowedParameters(), + this.reportingProcessServiceProvider.findAllReportingTypes()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, result, this.responseDataParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java index d25710340..0f0c4c5b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java @@ -25,10 +25,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import java.io.File; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -40,20 +36,14 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; -import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; -import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; -import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; -import org.apache.fineract.infrastructure.dataqueries.data.ReportData; -import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService; +import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService; import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider; import org.apache.fineract.infrastructure.report.service.ReportingProcessService; import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator; import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; @@ -65,20 +55,17 @@ import org.springframework.stereotype.Component; @Tag(name = "Run Reports", description = "") public class RunreportsApiResource { + public static final String IS_SELF_SERVICE_USER_REPORT_PARAMETER = "isSelfServiceUserReport"; + private final PlatformSecurityContext context; - private final ToApiJsonSerializer toApiJsonSerializer; private final ReadReportingService readExtraDataAndReportingService; - private final GenericDataService genericDataService; private final ReportingProcessServiceProvider reportingProcessServiceProvider; @Autowired public RunreportsApiResource(final PlatformSecurityContext context, final ReadReportingService readExtraDataAndReportingService, - final GenericDataService genericDataService, final ToApiJsonSerializer toApiJsonSerializer, final ReportingProcessServiceProvider reportingProcessServiceProvider) { this.context = context; this.readExtraDataAndReportingService = readExtraDataAndReportingService; - this.genericDataService = genericDataService; - this.toApiJsonSerializer = toApiJsonSerializer; this.reportingProcessServiceProvider = reportingProcessServiceProvider; } @@ -108,79 +95,26 @@ public class RunreportsApiResource { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RunreportsApiResourceSwagger.RunReportsResponse.class))) }) public Response runReport(@PathParam("reportName") @Parameter(description = "reportName") final String reportName, @Context final UriInfo uriInfo, - @DefaultValue("false") @QueryParam("isSelfServiceUserReport") @Parameter(description = "isSelfServiceUserReport") final boolean isSelfServiceUserReport) { + @DefaultValue("false") @QueryParam(IS_SELF_SERVICE_USER_REPORT_PARAMETER) @Parameter(description = IS_SELF_SERVICE_USER_REPORT_PARAMETER) final boolean isSelfServiceUserReport) { final MultivaluedMap queryParams = uriInfo.getQueryParameters(); - - final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - final boolean exportCsv = ApiParameterHelper.exportCsv(uriInfo.getQueryParameters()); - final boolean parameterType = ApiParameterHelper.parameterType(uriInfo.getQueryParameters()); - final boolean exportPdf = ApiParameterHelper.exportPdf(uriInfo.getQueryParameters()); + final boolean parameterType = ApiParameterHelper.parameterType(queryParams); checkUserPermissionForReport(reportName, parameterType); - String parameterTypeValue = null; - if (!parameterType) { - parameterTypeValue = "report"; - String reportType = this.readExtraDataAndReportingService.getReportType(reportName, isSelfServiceUserReport); - ReportingProcessService reportingProcessService = this.reportingProcessServiceProvider.findReportingProcessService(reportType); - if (reportingProcessService != null) { - return reportingProcessService.processRequest(reportName, queryParams); - } - } else { - parameterTypeValue = "parameter"; + // Pass through isSelfServiceUserReport so that ReportingProcessService implementations can use it + queryParams.putSingle(IS_SELF_SERVICE_USER_REPORT_PARAMETER, Boolean.toString(isSelfServiceUserReport)); + + String reportType = this.readExtraDataAndReportingService.getReportType(reportName, isSelfServiceUserReport); + ReportingProcessService reportingProcessService = this.reportingProcessServiceProvider.findReportingProcessService(reportType); + if (reportingProcessService == null) { + throw new PlatformServiceUnavailableException("err.msg.report.service.implementation.missing", + ReportingProcessServiceProvider.SERVICE_MISSING + reportType, reportType); } - - // PDF format - - if (exportPdf) { - final Map reportParams = getReportParams(queryParams); - final String pdfFileName = this.readExtraDataAndReportingService.retrieveReportPDF(reportName, parameterTypeValue, reportParams, - isSelfServiceUserReport); - - final File file = new File(pdfFileName); - - final ResponseBuilder response = Response.ok(file); - response.header("Content-Disposition", "attachment; filename=\"" + pdfFileName + "\""); - response.header("content-Type", "application/pdf"); - - return response.build(); - - } - - if (!exportCsv) { - final Map reportParams = getReportParams(queryParams); - - final GenericResultsetData result = this.readExtraDataAndReportingService.retrieveGenericResultset(reportName, - parameterTypeValue, reportParams, isSelfServiceUserReport); - - String json = ""; - final boolean genericResultSetIsPassed = ApiParameterHelper.genericResultSetPassed(uriInfo.getQueryParameters()); - final boolean genericResultSet = ApiParameterHelper.genericResultSet(uriInfo.getQueryParameters()); - if (genericResultSetIsPassed) { - if (genericResultSet) { - json = this.toApiJsonSerializer.serializePretty(prettyPrint, result); - } else { - json = this.genericDataService.generateJsonFromGenericResultsetData(result); - } - } else { - json = this.toApiJsonSerializer.serializePretty(prettyPrint, result); - } - - return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build(); - } - - // CSV Export - final Map reportParams = getReportParams(queryParams); - final StreamingOutput result = this.readExtraDataAndReportingService.retrieveReportCSV(reportName, parameterTypeValue, reportParams, - isSelfServiceUserReport); - - return Response.ok().entity(result).type("text/csv") - .header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build(); + return reportingProcessService.processRequest(reportName, queryParams); } private void checkUserPermissionForReport(final String reportName, final boolean parameterType) { - // Anyone can run a 'report' that is simply getting possible parameter // (dropdown listbox) values. if (!parameterType) { @@ -190,22 +124,4 @@ public class RunreportsApiResource { } } } - - private Map getReportParams(final MultivaluedMap queryParams) { - - final Map reportParams = new HashMap<>(); - final Set keys = queryParams.keySet(); - String pKey; - String pValue; - for (final String k : keys) { - - if (k.startsWith("R_")) { - pKey = "${" + k.substring(2) + "}"; - pValue = queryParams.get(k).get(0); - SQLInjectionValidator.validateSQLInput(pValue); - reportParams.put(pKey, pValue); - } - } - return reportParams; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableReportingProcessService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableReportingProcessService.java new file mode 100644 index 000000000..e7cb15653 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableReportingProcessService.java @@ -0,0 +1,110 @@ +/** + * 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 java.io.File; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.StreamingOutput; +import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; +import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; +import org.apache.fineract.infrastructure.dataqueries.api.RunreportsApiResource; +import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; +import org.apache.fineract.infrastructure.dataqueries.data.ReportData; +import org.apache.fineract.infrastructure.report.annotation.ReportService; +import org.apache.fineract.infrastructure.report.service.ReportingProcessService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@ReportService(type = { "Table", "Chart", "SMS" }) +public class DatatableReportingProcessService implements ReportingProcessService { + + private final ReadReportingService readExtraDataAndReportingService; + private final ToApiJsonSerializer toApiJsonSerializer; + private final GenericDataService genericDataService; + + @Autowired + public DatatableReportingProcessService(final ReadReportingService readExtraDataAndReportingService, + final GenericDataService genericDataService, final ToApiJsonSerializer toApiJsonSerializer) { + this.readExtraDataAndReportingService = readExtraDataAndReportingService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.genericDataService = genericDataService; + } + + @Override + public Response processRequest(String reportName, MultivaluedMap queryParams) { + boolean isSelfServiceUserReport = Boolean.parseBoolean( + queryParams.getOrDefault(RunreportsApiResource.IS_SELF_SERVICE_USER_REPORT_PARAMETER, List.of("false")).get(0)); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(queryParams); + final boolean exportCsv = ApiParameterHelper.exportCsv(queryParams); + final boolean exportPdf = ApiParameterHelper.exportPdf(queryParams); + final String parameterTypeValue = ApiParameterHelper.parameterType(queryParams) ? "parameter" : "report"; + + // PDF format + if (exportPdf) { + final Map reportParams = getReportParams(queryParams); + final String pdfFileName = this.readExtraDataAndReportingService.retrieveReportPDF(reportName, parameterTypeValue, reportParams, + isSelfServiceUserReport); + + final File file = new File(pdfFileName); + + final ResponseBuilder response = Response.ok(file); + response.header("Content-Disposition", "attachment; filename=\"" + pdfFileName + "\""); + response.header("content-Type", "application/pdf"); + + return response.build(); + } + + // JSON format + if (!exportCsv) { + final Map reportParams = getReportParams(queryParams); + + final GenericResultsetData result = this.readExtraDataAndReportingService.retrieveGenericResultset(reportName, + parameterTypeValue, reportParams, isSelfServiceUserReport); + + String json; + final boolean genericResultSetIsPassed = ApiParameterHelper.genericResultSetPassed(queryParams); + final boolean genericResultSet = ApiParameterHelper.genericResultSet(queryParams); + if (genericResultSetIsPassed) { + if (genericResultSet) { + json = this.toApiJsonSerializer.serializePretty(prettyPrint, result); + } else { + json = this.genericDataService.generateJsonFromGenericResultsetData(result); + } + } else { + json = this.toApiJsonSerializer.serializePretty(prettyPrint, result); + } + + return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build(); + } + + // CSV format + final Map reportParams = getReportParams(queryParams); + final StreamingOutput result = this.readExtraDataAndReportingService.retrieveReportCSV(reportName, parameterTypeValue, reportParams, + isSelfServiceUserReport); + + return Response.ok().entity(result).type("text/csv") + .header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java index dd0ef88eb..cf4b38314 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java @@ -30,29 +30,28 @@ import org.apache.fineract.useradministration.domain.AppUser; public interface ReadReportingService { + Collection retrieveReportList(); + + ReportData retrieveReport(Long id); + + String getReportType(String reportName, boolean isSelfServiceUserReport); + + Collection getAllowedParameters(); + + // TODO Move the following x3 methods into the (new; FINERACT-1173) DatatableReportingProcessService? + + String retrieveReportPDF(String name, String type, Map extractedQueryParams, boolean isSelfServiceUserReport); + StreamingOutput retrieveReportCSV(String name, String type, Map extractedQueryParams, boolean isSelfServiceUserReport); GenericResultsetData retrieveGenericResultset(String name, String type, Map extractedQueryParams, boolean isSelfServiceUserReport); - String retrieveReportPDF(String name, String type, Map extractedQueryParams, boolean isSelfServiceUserReport); - - String getReportType(String reportName, boolean isSelfServiceUserReport); - - Collection retrieveReportList(); - - Collection getAllowedParameters(); - - ReportData retrieveReport(Long id); - - Collection getAllowedReportTypes(); - - // needed for smsCampaign and emailCampaign jobs where securityContext is - // null + // TODO This is weird, could they not be using the retrieveGenericResultset() above after all? + // needed for smsCampaign and emailCampaign jobs where securityContext is null GenericResultsetData retrieveGenericResultSetForSmsEmailCampaign(String name, String type, Map extractedQueryParams); - String sqlToRunForSmsEmailCampaign(String name, String type, Map queryParams); - + // TODO kill this when tackling https://issues.apache.org/jira/browse/FINERACT-1264 ByteArrayOutputStream generatePentahoReportAsOutputStream(String reportName, String outputTypeParam, Map queryParams, Locale locale, AppUser runReportAsUser, StringBuilder errorLog); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java index 7688caa36..43584f358 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java @@ -27,7 +27,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; -import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.sql.ResultSet; import java.sql.SQLException; @@ -51,7 +50,6 @@ import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeader import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData; import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException; import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository; -import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.infrastructure.security.utils.SQLInjectionException; @@ -74,53 +72,43 @@ public class ReadReportingServiceImpl implements ReadReportingService { private final DataSource dataSource; private final PlatformSecurityContext context; private final GenericDataService genericDataService; - private final ReportingProcessServiceProvider reportingProcessServiceProvider; private final ColumnValidator columnValidator; @Autowired public ReadReportingServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, - final GenericDataService genericDataService, final ReportingProcessServiceProvider reportingProcessServiceProvider, - final ColumnValidator columnValidator) { - + final GenericDataService genericDataService, final ColumnValidator columnValidator) { this.context = context; this.dataSource = dataSource; this.jdbcTemplate = new JdbcTemplate(this.dataSource); this.genericDataService = genericDataService; - this.reportingProcessServiceProvider = reportingProcessServiceProvider; this.columnValidator = columnValidator; } @Override public StreamingOutput retrieveReportCSV(final String name, final String type, final Map queryParams, final boolean isSelfServiceUserReport) { + return out -> { + try { - return new StreamingOutput() { + final GenericResultsetData result = retrieveGenericResultset(name, type, queryParams, isSelfServiceUserReport); + final StringBuilder sb = generateCsvFileBuffer(result); - @Override - public void write(final OutputStream out) { - try { + final InputStream in = new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)); - final GenericResultsetData result = retrieveGenericResultset(name, type, queryParams, isSelfServiceUserReport); - final StringBuilder sb = generateCsvFileBuffer(result); + final byte[] outputByte = new byte[4096]; + Integer readLen = in.read(outputByte, 0, 4096); - final InputStream in = new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)); - - final byte[] outputByte = new byte[4096]; - Integer readLen = in.read(outputByte, 0, 4096); - - while (readLen != -1) { - out.write(outputByte, 0, readLen); - readLen = in.read(outputByte, 0, 4096); - } - // in.close(); - // out.flush(); - // out.close(); - } catch (final Exception e) { - throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage(), e); + while (readLen != -1) { + out.write(outputByte, 0, readLen); + readLen = in.read(outputByte, 0, 4096); } + // in.close(); + // out.flush(); + // out.close(); + } catch (final Exception e) { + throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage(), e); } }; - } private StringBuilder generateCsvFileBuffer(final GenericResultsetData result) { @@ -146,8 +134,8 @@ public class ReadReportingServiceImpl implements ReadReportingService { final String doubleQuote = "\""; final String twoDoubleQuotes = doubleQuote + doubleQuote; LOG.info("NO. of Rows: {}", data.size()); - for (int i = 0; i < data.size(); i++) { - row = data.get(i).getRow(); + for (ResultsetRowData element : data) { + row = element.getRow(); rSize = row.size(); for (int j = 0; j < rSize; j++) { // currCol = columnHeaders.get(j).getColumnName(); @@ -184,7 +172,7 @@ public class ReadReportingServiceImpl implements ReadReportingService { final GenericResultsetData result = this.genericDataService.fillGenericResultSet(sql); final long elapsed = System.currentTimeMillis() - startTime; - LOG.info("FINISHING Report/Request Name: {} - {} Elapsed Time: {}", new Object[] { name, type, elapsed }); + LOG.info("FINISHING Report/Request Name: {} - {} Elapsed Time: {}", name, type, elapsed); return result; } @@ -213,11 +201,9 @@ public class ReadReportingServiceImpl implements ReadReportingService { sql = this.genericDataService.wrapSQL(sql); return sql; - } private String getSql(final String name, final String type) { - final String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = '" + name + "'"; validateReportName(name); @@ -234,7 +220,6 @@ public class ReadReportingServiceImpl implements ReadReportingService { @Override public String getReportType(final String reportName, final boolean isSelfServiceUserReport) { - final String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" + reportName + "' and self_service_user_report = ?"; validateReportName(reportName); @@ -242,7 +227,7 @@ public class ReadReportingServiceImpl implements ReadReportingService { final String sqlWrapped = this.genericDataService.wrapSQL(sql); - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sqlWrapped, new Object[] { isSelfServiceUserReport }); + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sqlWrapped, isSelfServiceUserReport); if (rs.next()) { return rs.getString("report_type"); @@ -290,8 +275,8 @@ public class ReadReportingServiceImpl implements ReadReportingService { String currColType; String currVal; LOG.info("NO. of Rows: {}", data.size()); - for (int i = 0; i < data.size(); i++) { - row = data.get(i).getRow(); + for (ResultsetRowData element : data) { + row = element.getRow(); rSize = row.size(); for (int j = 0; j < rSize; j++) { currColType = columnHeaders.get(j).getColumnType(); @@ -338,7 +323,7 @@ public class ReadReportingServiceImpl implements ReadReportingService { final String sql = rm.schema(id); - final Collection rpJoins = this.jdbcTemplate.query(sql, rm, new Object[] {}); + final Collection rpJoins = this.jdbcTemplate.query(sql, rm); final Collection reportList = new ArrayList<>(); if (rpJoins == null || rpJoins.size() == 0) { @@ -410,22 +395,12 @@ public class ReadReportingServiceImpl implements ReadReportingService { @Override public Collection getAllowedParameters() { - final ReportParameterMapper rm = new ReportParameterMapper(); final String sql = rm.schema(); - final Collection parameters = this.jdbcTemplate.query(sql, rm, new Object[] {}); + final Collection parameters = this.jdbcTemplate.query(sql, rm); return parameters; } - @Override - public Collection getAllowedReportTypes() { - final List reportTypes = new ArrayList<>(); - reportTypes.add("Table"); - reportTypes.add("Chart"); - reportTypes.addAll(this.reportingProcessServiceProvider.findAllReportingTypes()); - return reportTypes; - } - private static final class ReportParameterJoinMapper implements RowMapper { public String schema(final Long reportId) { @@ -460,7 +435,6 @@ public class ReadReportingServiceImpl implements ReadReportingService { @Override public ReportParameterJoinData mapRow(final ResultSet rs, final int rowNum) throws SQLException { - final Long reportId = rs.getLong("reportId"); final String reportName = rs.getString("reportName"); final String reportType = rs.getString("reportType"); @@ -491,9 +465,7 @@ public class ReadReportingServiceImpl implements ReadReportingService { private static final class ReportParameterMapper implements RowMapper { public String schema() { - return "select p.id as id, p.parameter_name as parameterName from stretchy_parameter p where ifnull(p.special,'') != 'Y' order by p.id"; - } @Override @@ -516,12 +488,11 @@ public class ReadReportingServiceImpl implements ReadReportingService { final GenericResultsetData result = this.genericDataService.fillGenericResultSet(sql); final long elapsed = System.currentTimeMillis() - startTime; - LOG.info("FINISHING Report/Request Name: {} - {} Elapsed Time: {}", new Object[] { name, type, elapsed }); + LOG.info("FINISHING Report/Request Name: {} - {} Elapsed Time: {}", name, type, elapsed); return result; } - @Override - public String sqlToRunForSmsEmailCampaign(final String name, final String type, final Map queryParams) { + private String sqlToRunForSmsEmailCampaign(final String name, final String type, final Map queryParams) { String sql = getSql(name, type); final Set keys = queryParams.keySet(); @@ -599,7 +570,6 @@ public class ReadReportingServiceImpl implements ReadReportingService { } private void validateReportName(final String name) { - if (!StringUtils.isBlank(name) && !name.matches(REPORT_NAME_REGEX_PATTERN)) { throw new SQLInjectionException(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformServiceImpl.java index 8794b585c..35069bee0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReportWritePlatformServiceImpl.java @@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository; import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException; import org.apache.fineract.infrastructure.dataqueries.exception.ReportParameterNotFoundException; import org.apache.fineract.infrastructure.dataqueries.serialization.ReportCommandFromApiJsonDeserializer; +import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.useradministration.domain.Permission; import org.apache.fineract.useradministration.domain.PermissionRepository; @@ -62,20 +63,20 @@ public class ReportWritePlatformServiceImpl implements ReportWritePlatformServic private final ReportParameterUsageRepository reportParameterUsageRepository; private final ReportParameterRepository reportParameterRepository; private final PermissionRepository permissionRepository; - private final ReadReportingService readReportingService; + private final ReportingProcessServiceProvider reportingProcessServiceProvider; @Autowired public ReportWritePlatformServiceImpl(final PlatformSecurityContext context, final ReportCommandFromApiJsonDeserializer fromApiJsonDeserializer, final ReportRepository reportRepository, final ReportParameterRepository reportParameterRepository, final ReportParameterUsageRepository reportParameterUsageRepository, - final PermissionRepository permissionRepository, final ReadReportingService readReportingService) { + final PermissionRepository permissionRepository, final ReportingProcessServiceProvider reportingProcessServiceProvider) { this.context = context; this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.reportRepository = reportRepository; this.reportParameterRepository = reportParameterRepository; this.reportParameterUsageRepository = reportParameterUsageRepository; this.permissionRepository = permissionRepository; - this.readReportingService = readReportingService; + this.reportingProcessServiceProvider = reportingProcessServiceProvider; } @Transactional @@ -87,7 +88,7 @@ public class ReportWritePlatformServiceImpl implements ReportWritePlatformServic this.fromApiJsonDeserializer.validate(command.json()); - final Report report = Report.fromJson(command, this.readReportingService.getAllowedReportTypes()); + final Report report = Report.fromJson(command, this.reportingProcessServiceProvider.findAllReportingTypes()); final Set reportParameterUsages = assembleSetOfReportParameterUsages(report, command); report.update(reportParameterUsages); @@ -121,7 +122,7 @@ public class ReportWritePlatformServiceImpl implements ReportWritePlatformServic final Report report = this.reportRepository.findById(reportId).orElseThrow(() -> new ReportNotFoundException(reportId)); - final Map changes = report.update(command, this.readReportingService.getAllowedReportTypes()); + final Map changes = report.update(command, this.reportingProcessServiceProvider.findAllReportingTypes()); if (changes.containsKey("reportParameters")) { final Set reportParameterUsages = assembleSetOfReportParameterUsages(report, command); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/annotation/ReportService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/annotation/ReportService.java index 8c72b1bc4..a101e1fb5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/annotation/ReportService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/annotation/ReportService.java @@ -32,5 +32,5 @@ public @interface ReportService { /** * @return the type of the report */ - String type(); + String[] type(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java index c52b1c3d0..84e812a67 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java @@ -18,68 +18,46 @@ */ package org.apache.fineract.infrastructure.report.provider; +import com.google.common.collect.ImmutableMap; import java.util.Collection; -import java.util.HashMap; +import java.util.List; import java.util.Map; -import org.apache.commons.lang3.ArrayUtils; import org.apache.fineract.infrastructure.report.annotation.ReportService; import org.apache.fineract.infrastructure.report.service.ReportingProcessService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("singleton") -public class ReportingProcessServiceProvider implements ApplicationContextAware { +public class ReportingProcessServiceProvider { + + public static final String SERVICE_MISSING = "There is no ReportingProcessService registered in the ReportingProcessServiceProvider for this report type: "; private static final Logger LOGGER = LoggerFactory.getLogger(ReportingProcessServiceProvider.class); - private ApplicationContext applicationContext; - - Map reportingProcessServices = null; - - ReportingProcessServiceProvider() { + private final Map reportingProcessServices; + @Autowired + public ReportingProcessServiceProvider(List reportingProcessServices) { + var mapBuilder = ImmutableMap.builder(); + for (ReportingProcessService s : reportingProcessServices) { + String[] reportTypes = s.getClass().getAnnotation(ReportService.class).type(); + for (String type : reportTypes) { + mapBuilder.put(type, s); + } + LOGGER.info("Registered report service '{}' for type/s '{}'", s, reportTypes); + } + this.reportingProcessServices = mapBuilder.build(); } public ReportingProcessService findReportingProcessService(final String reportType) { - if (this.reportingProcessServices.containsKey(reportType)) { - return (ReportingProcessService) this.applicationContext.getBean(this.reportingProcessServices.get(reportType)); - } - return null; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - this.initializeRegistry(); + return reportingProcessServices.getOrDefault(reportType, null); } public Collection findAllReportingTypes() { return this.reportingProcessServices.keySet(); - - } - - private void initializeRegistry() { - if (this.reportingProcessServices == null) { - this.reportingProcessServices = new HashMap<>(); - - final String[] reportServiceBeans = this.applicationContext.getBeanNamesForAnnotation(ReportService.class); - if (ArrayUtils.isNotEmpty(reportServiceBeans)) { - for (final String reportName : reportServiceBeans) { - LOGGER.info("Register report service '{}' ...", reportName); - final ReportService service = this.applicationContext.findAnnotationOnBean(reportName, ReportService.class); - try { - this.reportingProcessServices.put(service.type(), reportName); - } catch (final Throwable th) { - LOGGER.error("Unable to register reporting service '{}'!", reportName, th); - } - } - } - } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java index 990bd0d91..406157f54 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/service/ReportingProcessService.java @@ -18,11 +18,28 @@ */ package org.apache.fineract.infrastructure.report.service; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator; public interface ReportingProcessService { Response processRequest(String reportName, MultivaluedMap queryParams); + default Map getReportParams(final MultivaluedMap queryParams) { + final Map reportParams = new HashMap<>(); + final Set keys = queryParams.keySet(); + for (final String k : keys) { + if (k.startsWith("R_")) { + String pKey = "${" + k.substring(2) + "}"; + String pValue = queryParams.get(k).get(0); + SQLInjectionValidator.validateSQLInput(pValue); + reportParams.put(pKey, pValue); + } + } + return reportParams; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java index 47d45ec59..1de29e37c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java @@ -471,7 +471,7 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo errorLog.append("Response object entity is not equal to ByteArrayOutputStream ---------- "); } } else { - errorLog.append("ReportingProcessService object is null ---------- "); + errorLog.append(ReportingProcessServiceProvider.SERVICE_MISSING + reportType); } } catch (Exception e) { errorLog.append("The ReportMailingJobWritePlatformServiceImpl.generateReportOutputStream method threw an Exception: " + e diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java index 8eebfe959..03a11572c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java @@ -45,7 +45,7 @@ public class ReportsTest extends IntegrationTest { @Test void runExpectedPaymentsPentahoReportWithoutPlugin() { assertThat(fineract().reportsRun.runReportGetFile("Expected Payments By Date - Formatted", Map.of("R_endDate", "2013-04-30", - "R_loanOfficerId", "-1", "R_officeId", "1", "R_startDate", "2013-04-16", "output-type", "PDF"), false)).hasHttpStatus(404); + "R_loanOfficerId", "-1", "R_officeId", "1", "R_startDate", "2013-04-16", "output-type", "PDF"), false)).hasHttpStatus(503); } @Test