Improve error handling if reporting service implementation is missing [FINERACT-1173]

This commit is contained in:
Michael Vorburger
2020-11-26 06:13:15 +08:00
committed by Petri Tuomola
parent a3ab467278
commit b96061613b
11 changed files with 218 additions and 222 deletions
@@ -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<ReportData> 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);
@@ -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<ReportData> 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<ReportData> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> getReportParams(final MultivaluedMap<String, String> queryParams) {
final Map<String, String> reportParams = new HashMap<>();
final Set<String> 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;
}
}
@@ -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<ReportData> toApiJsonSerializer;
private final GenericDataService genericDataService;
@Autowired
public DatatableReportingProcessService(final ReadReportingService readExtraDataAndReportingService,
final GenericDataService genericDataService, final ToApiJsonSerializer<ReportData> toApiJsonSerializer) {
this.readExtraDataAndReportingService = readExtraDataAndReportingService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.genericDataService = genericDataService;
}
@Override
public Response processRequest(String reportName, MultivaluedMap<String, String> 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<String, String> 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<String, String> 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<String, String> 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();
}
}
@@ -30,29 +30,28 @@ import org.apache.fineract.useradministration.domain.AppUser;
public interface ReadReportingService {
Collection<ReportData> retrieveReportList();
ReportData retrieveReport(Long id);
String getReportType(String reportName, boolean isSelfServiceUserReport);
Collection<ReportParameterData> getAllowedParameters();
// TODO Move the following x3 methods into the (new; FINERACT-1173) DatatableReportingProcessService?
String retrieveReportPDF(String name, String type, Map<String, String> extractedQueryParams, boolean isSelfServiceUserReport);
StreamingOutput retrieveReportCSV(String name, String type, Map<String, String> extractedQueryParams, boolean isSelfServiceUserReport);
GenericResultsetData retrieveGenericResultset(String name, String type, Map<String, String> extractedQueryParams,
boolean isSelfServiceUserReport);
String retrieveReportPDF(String name, String type, Map<String, String> extractedQueryParams, boolean isSelfServiceUserReport);
String getReportType(String reportName, boolean isSelfServiceUserReport);
Collection<ReportData> retrieveReportList();
Collection<ReportParameterData> getAllowedParameters();
ReportData retrieveReport(Long id);
Collection<String> 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<String, String> extractedQueryParams);
String sqlToRunForSmsEmailCampaign(String name, String type, Map<String, String> queryParams);
// TODO kill this when tackling https://issues.apache.org/jira/browse/FINERACT-1264
ByteArrayOutputStream generatePentahoReportAsOutputStream(String reportName, String outputTypeParam, Map<String, String> queryParams,
Locale locale, AppUser runReportAsUser, StringBuilder errorLog);
}
@@ -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<String, String> 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<ReportParameterJoinData> rpJoins = this.jdbcTemplate.query(sql, rm, new Object[] {});
final Collection<ReportParameterJoinData> rpJoins = this.jdbcTemplate.query(sql, rm);
final Collection<ReportData> reportList = new ArrayList<>();
if (rpJoins == null || rpJoins.size() == 0) {
@@ -410,22 +395,12 @@ public class ReadReportingServiceImpl implements ReadReportingService {
@Override
public Collection<ReportParameterData> getAllowedParameters() {
final ReportParameterMapper rm = new ReportParameterMapper();
final String sql = rm.schema();
final Collection<ReportParameterData> parameters = this.jdbcTemplate.query(sql, rm, new Object[] {});
final Collection<ReportParameterData> parameters = this.jdbcTemplate.query(sql, rm);
return parameters;
}
@Override
public Collection<String> getAllowedReportTypes() {
final List<String> reportTypes = new ArrayList<>();
reportTypes.add("Table");
reportTypes.add("Chart");
reportTypes.addAll(this.reportingProcessServiceProvider.findAllReportingTypes());
return reportTypes;
}
private static final class ReportParameterJoinMapper implements RowMapper<ReportParameterJoinData> {
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<ReportParameterData> {
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<String, String> queryParams) {
private String sqlToRunForSmsEmailCampaign(final String name, final String type, final Map<String, String> queryParams) {
String sql = getSql(name, type);
final Set<String> 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();
}
@@ -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<ReportParameterUsage> 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<String, Object> changes = report.update(command, this.readReportingService.getAllowedReportTypes());
final Map<String, Object> changes = report.update(command, this.reportingProcessServiceProvider.findAllReportingTypes());
if (changes.containsKey("reportParameters")) {
final Set<ReportParameterUsage> reportParameterUsages = assembleSetOfReportParameterUsages(report, command);
@@ -32,5 +32,5 @@ public @interface ReportService {
/**
* @return the type of the report
*/
String type();
String[] type();
}
@@ -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<String, String> reportingProcessServices = null;
ReportingProcessServiceProvider() {
private final Map<String, ReportingProcessService> reportingProcessServices;
@Autowired
public ReportingProcessServiceProvider(List<ReportingProcessService> reportingProcessServices) {
var mapBuilder = ImmutableMap.<String, ReportingProcessService>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<String> 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);
}
}
}
}
}
}
@@ -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<String, String> queryParams);
default Map<String, String> getReportParams(final MultivaluedMap<String, String> queryParams) {
final Map<String, String> reportParams = new HashMap<>();
final Set<String> 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;
}
}
@@ -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
@@ -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