Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FINERACT-1094 Support for Pentaho Reports #1262

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions fineract-provider/build.gradle
Expand Up @@ -26,6 +26,7 @@ buildDir = new File(rootProject.projectDir, "../build")
repositories {
jcenter()
mavenCentral()
maven {url 'https://nexus.pentaho.org/content/groups/omni/'}
}

project.ext.jerseyVersion = '1.19.4'
Expand Down Expand Up @@ -136,6 +137,29 @@ dependencyManagement {
exclude 'pull-parser:pull-parser'
}

dependency 'pentaho-reporting-engine:pentaho-reporting-engine-classic-core:3.9.1.1'
dependencySet(group: 'pentaho-reporting-engine', version: '3.9.1-GA'){
entry 'pentaho-reporting-engine-classic-extensions'
entry 'pentaho-reporting-engine-classic-extensions-scripting'
entry 'pentaho-reporting-engine-wizard-core'
}

dependencySet(group: 'pentaho-report-designer', version: '3.9.1-GA'){
entry 'pentaho-reporting-engine-wizard-xul'
}

dependencySet(group: 'pentaho-library', version: '1.2.8'){
entry 'libbase'
entry 'libdocbundle'
entry 'libfonts'
entry 'libformat'
entry 'libformula'
entry 'libloader'
entry 'librepository'
entry 'libserializer'
entry 'libsparkline'
entry 'libxml'
}
dependencySet(group: 'com.sun.jersey', version: jerseyVersion) {
entry 'jersey-core'
entry 'jersey-servlet'
Expand Down Expand Up @@ -297,6 +321,7 @@ spotless {
// Configuration for Apache Release Audit Tool task
// https://github.com/eskatos/creadur-rat-gradle
rat {
//failOnError = false // from Luis' repo
verbose = false
reportDir = new File(buildDir,'reports/rat')
excludes = [
Expand Down
37 changes: 37 additions & 0 deletions fineract-provider/config/swagger/fineract-input.yaml
@@ -0,0 +1,37 @@
openapi: 3.0.3
info:
version: release-1.4.0-558-gc4de1bf-dirty
title: Apache Fineract
description: |-
Apache Fineract is a secure, multi-tenanted microfinance platform

The goal of the Apache Fineract API is to empower developers to build apps on top of the Apache Fineract Platform<br>The [reference app](https://cui.fineract.dev) (username: mifos, password: password) works on the same demo tenant as the interactive links in this documentation

- The API is organized around [REST](https://en.wikipedia.org/wiki/Representational_state_transfer)
- Find out more about Apache Fineract [here](/fineract-provider/api-docs/apiLive.htm#top)
- You can [Try The API From Your Browser](/fineract-provider/api-docs/apiLive.htm#interact)
- The Generic Options are available [here](/fineract-provider/api-docs/apiLive.htm#genopts)
- Find out more about [Updating Dates and Numbers](/fineract-provider/api-docs/apiLive.htm#dates_and_numbers)
- For the Authentication and the Basic of HTTP and HTTPS refer [here](/fineract-provider/api-docs/apiLive.htm#authentication_overview)
- Check about ERROR codes [here](/fineract-provider/api-docs/apiLive.htm#errors)

Please refer to the [old documentation](/fineract-provider/api-docs/apiLive.htm) for any documentation queries
contact:
email: dev@fineract.apache.org
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
servers:
- url: https://demo.fineract.dev/fineract-provider/api/v1
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
tenantid:
type: apiKey
in: header
name: fineract-platform-tenantid
security:
- basicAuth: []
tenantid: []
17 changes: 17 additions & 0 deletions fineract-provider/dependencies.gradle
Expand Up @@ -78,6 +78,23 @@ dependencies {

'javax.cache:cache-api',

'pentaho-reporting-engine:pentaho-reporting-engine-classic-core',
'pentaho-reporting-engine:pentaho-reporting-engine-classic-extensions',
'pentaho-reporting-engine:pentaho-reporting-engine-classic-extensions-scripting',
'pentaho-reporting-engine:pentaho-reporting-engine-wizard-core',
'pentaho-report-designer:pentaho-reporting-engine-wizard-xul',

'pentaho-library:libbase',
'pentaho-library:libdocbundle',
'pentaho-library:libfonts',
'pentaho-library:libformat',
'pentaho-library:libformula',
'pentaho-library:libloader',
'pentaho-library:librepository',
'pentaho-library:libserializer',
'pentaho-library:libsparkline',
'pentaho-library:libxml',

'com.github.spotbugs:spotbugs-annotations',
'io.swagger.core.v3:swagger-annotations',
'org.webjars:webjars-locator-core'
Expand Down
@@ -0,0 +1,259 @@
/**
* 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.report.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.sql.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.boot.JDBCDriverConfig;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.report.annotation.ReportService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.useradministration.domain.AppUser;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.DefaultReportEnvironment;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelReportUtil;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry;
import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterDefinition;
import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@ReportService(type = "Pentaho")
public class PentahoReportingProcessServiceImpl implements ReportingProcessService {

private static final Logger logger = LoggerFactory.getLogger(PentahoReportingProcessServiceImpl.class);
public static final String MIFOS_BASE_DIR = System.getProperty("user.home") + File.separator + ".mifosx";

private final PlatformSecurityContext context;
private boolean noPentaho = false;

@Autowired
private JDBCDriverConfig driverConfig;

@Autowired
public PentahoReportingProcessServiceImpl(final PlatformSecurityContext context) {
// kick off pentaho reports server
ClassicEngineBoot.getInstance().start();
this.noPentaho = false;

this.context = context;
}

@Override
public Response processRequest(final String reportName, final MultivaluedMap<String, String> queryParams) {

final String outputTypeParam = queryParams.getFirst("output-type");
final Map<String, String> reportParams = getReportParams(queryParams);
final Locale locale = ApiParameterHelper.extractLocale(queryParams);

String outputType = "HTML";
if (StringUtils.isNotBlank(outputTypeParam)) {
outputType = outputTypeParam;
}

if (!(outputType.equalsIgnoreCase("HTML") || outputType.equalsIgnoreCase("PDF") || outputType.equalsIgnoreCase("XLS")
|| outputType.equalsIgnoreCase("XLSX") || outputType.equalsIgnoreCase("CSV"))) {
throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType);
}

if (this.noPentaho) {
throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled", "Pentaho is not enabled");
}

final String reportPath = MIFOS_BASE_DIR + File.separator + "pentahoReports" + File.separator + reportName + ".prpt";

String outPutInfo = "Report path: " + reportPath;
logger.info("Report path: {}", outPutInfo);

// load report definition
final ResourceManager manager = new ResourceManager();
manager.registerDefaults();
Resource res;

try {
res = manager.createDirectly(reportPath, MasterReport.class);
final MasterReport masterReport = (MasterReport) res.getResource();
final DefaultReportEnvironment reportEnvironment = (DefaultReportEnvironment) masterReport.getReportEnvironment();
if (locale != null) {
reportEnvironment.setLocale(locale);
}
addParametersToReport(masterReport, reportParams);

final ByteArrayOutputStream baos = new ByteArrayOutputStream();

if ("PDF".equalsIgnoreCase(outputType)) {
PdfReportUtil.createPDF(masterReport, baos);
return Response.ok().entity(baos.toByteArray()).type("application/pdf").build();
}

if ("XLS".equalsIgnoreCase(outputType)) {
ExcelReportUtil.createXLS(masterReport, baos);
return Response.ok().entity(baos.toByteArray()).type("application/vnd.ms-excel")
.header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".xls").build();
}

if ("XLSX".equalsIgnoreCase(outputType)) {
ExcelReportUtil.createXLSX(masterReport, baos);
return Response.ok().entity(baos.toByteArray()).type("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".xlsx").build();
}

if ("CSV".equalsIgnoreCase(outputType)) {
CSVReportUtil.createCSV(masterReport, baos, "UTF-8");
return Response.ok().entity(baos.toByteArray()).type("text/csv")
.header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build();
}

if ("HTML".equalsIgnoreCase(outputType)) {
HtmlReportUtil.createStreamHTML(masterReport, baos);
return Response.ok().entity(baos.toByteArray()).type("text/html").build();
}
} catch (final ResourceException e) {
// throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
logger.error("error.msg.reporting.error", e);
} catch (final ReportProcessingException e) {
// throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
logger.error("error.msg.reporting.error", e);
} catch (final IOException e) {
// throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
logger.error("error.msg.reporting.error", e);
}

throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType);
}

private void addParametersToReport(final MasterReport report, final Map<String, String> queryParams) {

final AppUser currentUser = this.context.authenticatedUser();

try {

final ReportParameterValues rptParamValues = report.getParameterValues();
final ReportParameterDefinition paramsDefinition = report.getParameterDefinition();

/*
* only allow integer, long, date and string parameter types and assume all mandatory - could go more
* detailed like Pawel did in Mifos later and could match incoming and pentaho parameters better...
* currently assuming they come in ok... and if not an error
*/
for (final ParameterDefinitionEntry paramDefEntry : paramsDefinition.getParameterDefinitions()) {
final String paramName = paramDefEntry.getName();
if (!(paramName.equals("tenantUrl") || (paramName.equals("userhierarchy") || paramName.equals("username")
|| (paramName.equals("password") || paramName.equals("userid"))))) {

String outPutInfo2 = "paramName:" + paramName;
logger.info("paramName: {}", outPutInfo2);

final String pValue = queryParams.get(paramName);
if (StringUtils.isBlank(pValue)) {
throw new PlatformDataIntegrityException("error.msg.reporting.error",
"Pentaho Parameter: " + paramName + " - not Provided");
}

final Class<?> clazz = paramDefEntry.getValueType();

String outPutInfo3 = "addParametersToReport(" + paramName + " : " + pValue + " : " + clazz.getCanonicalName() + ")";
logger.info("outputInfo: {}", outPutInfo3);

if (clazz.getCanonicalName().equalsIgnoreCase("java.lang.Integer")) {
rptParamValues.put(paramName, Integer.parseInt(pValue));
} else if (clazz.getCanonicalName().equalsIgnoreCase("java.lang.Long")) {
rptParamValues.put(paramName, Long.parseLong(pValue));
} else if (clazz.getCanonicalName().equalsIgnoreCase("java.sql.Date")) {
rptParamValues.put(paramName, Date.valueOf(pValue));
} else {
rptParamValues.put(paramName, pValue);
}
}

}

// tenant database name and current user's office hierarchy
// passed as parameters to allow multitenant penaho reporting
// and
// data scoping
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
final FineractPlatformTenantConnection tenantConnection = tenant.getConnection();
String tenantUrl = driverConfig.constructProtocol(tenantConnection.getSchemaServer(), tenantConnection.getSchemaServerPort(),
tenantConnection.getSchemaName());
final String userhierarchy = currentUser.getOffice().getHierarchy();
String outPutInfo4 = "db URL:" + tenantUrl + " userhierarchy:" + userhierarchy;
logger.info(outPutInfo4);

rptParamValues.put("userhierarchy", userhierarchy);

final Long userid = currentUser.getId();
String outPutInfo5 = "db URL:" + tenantUrl + " userid:" + userid;
logger.info(outPutInfo5);

rptParamValues.put("userid", userid);

rptParamValues.put("tenantUrl", tenantUrl);
rptParamValues.put("username", tenantConnection.getSchemaUsername());
rptParamValues.put("password", tenantConnection.getSchemaPassword());
} catch (final Exception e) {
// logger.error("error.msg.reporting.error:" + e.getMessage());
logger.error("error.msg.reporting.error:", e);
// throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
}
}

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);
reportParams.put(pKey, pValue);
}
}
return reportParams;
}

}
Expand Up @@ -106,8 +106,6 @@ protected boolean isHarmlessDuplicate(String resourcePath) {
|| resourcePath.equals("META-INF/git.properties") || resourcePath.equals("META-INF/io.netty.versions.properties")
|| resourcePath.equals("META-INF/jersey-module-version") || resourcePath.startsWith("OSGI-INF/blueprint/")
|| resourcePath.startsWith("org/opendaylight/blueprint/") || resourcePath.endsWith("reference.conf") // in
// Akka's
// JARs
// json-schema-core and json-schema-validator depend on each
// other and include these files
|| resourcePath.equals("draftv4/schema") || resourcePath.equals("draftv3/schema") //
Expand Down Expand Up @@ -163,6 +161,20 @@ protected boolean isHarmlessDuplicate(String resourcePath) {
// errorprone with Java 11 integration leaks to classpath, which
// causes a conflict between
// checkerframework/checker-qual and checkerframework/dataflow
|| resourcePath.startsWith("org/checkerframework/dataflow/qual/");
|| resourcePath.startsWith("org/checkerframework/dataflow/qual/")
// ClasspathHell from adding Pentaho Reports Support
// overview.html
|| resourcePath.endsWith("overview.html") || resourcePath.contains("org.mnode.ical4j/ical4j/3.0.19")
|| resourcePath.endsWith("overview.html")
|| resourcePath.contains("pentaho-reporting-engine/pentaho-reporting-engine-classic")
|| resourcePath.endsWith("overview.html") || resourcePath.contains("pentaho-library/lib")
// classic-engine.properties
|| resourcePath.endsWith("classic-engine.properties")
|| resourcePath.contains("pentaho-reporting-engine/pentaho-reporting-engine")
// loader.properties
|| resourcePath.endsWith("loader.properties") || resourcePath.contains("pentaho-reporting-engine/pentaho-reporting-engine")
|| resourcePath.endsWith("loader.properties") || resourcePath.contains("pentaho-library/lib")

;
}
}