Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Upcoming Version / (WIP)

Improvements:
* Stabilized consul registration and health checks
* [ODRC-24](https://openlmis.atlassian.net/browse/ODRC-24) Global header and translations implemented for reports

1.5.0 / 2025-11-27
==================
Expand Down
3 changes: 2 additions & 1 deletion dependency.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ dependencies {
compile "org.openlmis:openlmis-service-util:3.0.0"

compile "commons-codec:commons-codec"
compile "commons-io:commons-io:2.4"
compile "commons-io:commons-io:2.7"
compile "net.sf.jasperreports:jasperreports:6.5.1"
compile "net.sf.jasperreports:jasperreports-fonts:6.0.0"
compile "org.apache.commons:commons-lang3"
compile "org.flywaydb:flyway-core"
compile 'org.javers:javers-spring-boot-starter-sql:5.13.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void generateReportShouldNotThrowErrorForAnyOfTheSupportedFormats()
@Test(expected = JasperReportViewException.class)
public void shouldThrowJasperReportViewExceptionInsteadOfNullPointerException()
throws JasperReportViewException {
service.getJasperReportsView(null, null);
service.getJasperReportsView((byte[]) null, null);
}

@Test (expected = JasperReportViewException.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* This program is part of the OpenLMIS logistics management information system platform software.
* Copyright © 2017 VillageReach
*
* This program is free software: you can redistribute it and/or modify it under the terms
* of the GNU Affero General Public License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details. You should have received a copy of
* the GNU Affero General Public License along with this program. If not, see
* http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
*/

package org.openlmis.report.dto.external;

import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
public final class GenerateReportDto {
private String name;
private byte[] template;
private Map<String, Object> params;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
package org.openlmis.report.exception;

public class JasperReportViewException extends BaseLocalizedException {
public JasperReportViewException(String messageKey, String... params) {
super(messageKey, params);
}

public JasperReportViewException(Throwable cause, String messageKey, String... params) {
super(cause, messageKey, params);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,103 @@
import static org.openlmis.report.i18n.JasperMessageKeys.ERROR_JASPER_REPORT_GENERATION;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.sql.Connection;
import java.util.Collection;
import java.util.Map;
import javax.sql.DataSource;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import org.apache.commons.io.serialization.ValidatingObjectInputStream;
import org.openlmis.report.domain.JasperTemplate;
import org.openlmis.report.exception.JasperReportViewException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class JasperReportsViewService {
private static final String PARAM_DATASOURCE = "datasource";

@Autowired
private DataSource replicationDataSource;

/**
* Create Jasper Report View.
* Create Jasper Report (".jasper" file) from bytes from Template entity.
* Set 'Jasper' exporter parameters, JDBC data source, web application context, url to file.
* Create Jasper Report View. Create Jasper Report (".jasper" file) from bytes from Template
* entity. Set 'Jasper' exporter parameters, JDBC data source, web application context, url to
* file.
*
* @param jasperTemplate template that will be used to create a view
* @param params map of parameters
* @param template template that will be used to create a view (byte[])
* @param params map of parameters
* @return created jasper view.
* @throws JasperReportViewException if there will be any problem with creating the view.
*/
public byte[] getJasperReportsView(JasperTemplate jasperTemplate,
Map<String, Object> params) throws JasperReportViewException {
public byte[] getJasperReportsView(byte[] template, Map<String, Object> params)
throws JasperReportViewException {

try {
try (Connection connection = replicationDataSource.getConnection()) {
ObjectInputStream inputStream = new ObjectInputStream(
new ByteArrayInputStream(jasperTemplate.getData()));

JasperPrint jasperPrint = JasperFillManager
.fillReport((JasperReport) inputStream.readObject(), params, connection);
JasperReport jasperReport;
try (ByteArrayInputStream byteInputStream = new ByteArrayInputStream(template);
ValidatingObjectInputStream vois = new ValidatingObjectInputStream(byteInputStream)) {
vois.accept(
Comment thread
mgrochalskisoldevelo marked this conversation as resolved.
"net.sf.jasperreports.*",
"java.awt.*",
"java.util.*",
"java.lang.*",
"java.math.*",
"[Lnet.sf.jasperreports.*",
"[Ljava.awt.*",
"[Ljava.util.*",
"[Ljava.lang.*",
"[Ljava.math.*",
"[B"
);
jasperReport = (JasperReport) vois.readObject();
}

return prepareReport(jasperPrint, params);
JasperPrint jasperPrint;
if (params.containsKey(PARAM_DATASOURCE) && params.get(PARAM_DATASOURCE) != null) {
Object dataSourceParam = params.get(PARAM_DATASOURCE);
JRDataSource jrDataSource;
if (dataSourceParam instanceof JRDataSource) {

Check warning on line 82 in src/main/java/org/openlmis/report/service/JasperReportsViewService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this instanceof check and cast with 'instanceof JRDataSource jrdatasource'

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-report&issues=AZzncznv9A8E2e9CNSEw&open=AZzncznv9A8E2e9CNSEw&pullRequest=22
jrDataSource = (JRDataSource) dataSourceParam;
} else if (dataSourceParam instanceof Collection) {
jrDataSource = new JRBeanCollectionDataSource((Collection<?>) dataSourceParam);
} else {
throw new JasperReportViewException(ERROR_JASPER_REPORT_GENERATION);
}
jasperPrint = JasperFillManager.fillReport(jasperReport, params, jrDataSource);
} else {
try (Connection connection = replicationDataSource.getConnection()) {
jasperPrint = JasperFillManager.fillReport(jasperReport, params, connection);
}
}
return prepareReport(jasperPrint, params);
} catch (IllegalArgumentException iae) {
throw new JasperReportViewException(iae,
ERROR_JASPER_REPORT_FORMAT_UNKNOWN, iae.getMessage());
throw new JasperReportViewException(iae, ERROR_JASPER_REPORT_FORMAT_UNKNOWN,
iae.getMessage());
} catch (Exception e) {
throw new JasperReportViewException(e, ERROR_JASPER_REPORT_GENERATION);
}
}

/**
* Create Jasper Report View. Create Jasper Report (".jasper" file) from bytes from Template
* entity. Set 'Jasper' exporter parameters, JDBC data source, web application context, url to
* file.
*
* @param jasperTemplate template that will be used to create a view
* @param params map of parameters
* @return created jasper view.
* @throws JasperReportViewException if there will be any problem with creating the view.
*/
public byte[] getJasperReportsView(JasperTemplate jasperTemplate,
Map<String, Object> params) throws JasperReportViewException {
return getJasperReportsView(jasperTemplate.getData(), params);
}

private byte[] prepareReport(JasperPrint jasperPrint, Map<String, Object> params)
throws JRException {
return getJasperExporter((String) params.get("format"), jasperPrint).exportReport();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ public Map<String, Object> mapRequestParametersToTemplate(
for (JasperTemplateParameter templateParameter : templateParameters) {
String templateParameterName = templateParameter.getName();

for (String requestParamName : requestParameterMap.keySet()) {
for (Map.Entry<String, String[]> requestParamName : requestParameterMap.entrySet()) {

if (templateParameterName.equalsIgnoreCase(requestParamName)) {
if (templateParameterName.equalsIgnoreCase(requestParamName.getKey())) {
String requestParamValue = "";
if (requestParameterMap.get(templateParameterName).length > 0) {
requestParamValue = requestParameterMap.get(templateParameterName)[0];
if (requestParamName.getValue().length > 0) {
requestParamValue = requestParamName.getValue()[0];
}

if (!(isBlank(requestParamValue)
Expand Down Expand Up @@ -212,39 +212,58 @@ public Map<String, BufferedImage> mapReportImagesToTemplate(JasperTemplate templ
* @return the locale bundle parameters
* @throws MalformedURLException the malformed url exception
*/
public Map<String, Object> getLocaleBundleParameters(JasperReport parentReport,
String userLocaleString)
public Map<String, Object> getLocaleBundleParameters(String userLocaleString)
throws MalformedURLException {
// validate if report requires resource bundle or not
String resourceBundleName = parentReport != null ? parentReport.getResourceBundle() : null;
if (resourceBundleName == null || resourceBundleName.trim().isEmpty()) {
if (userLocaleString == null) {
return Collections.emptyMap();
}

Locale userLocale;
try {
// try to parse locale param else fallback to english
userLocale = new Locale.Builder().setLanguageTag(userLocaleString).build();
} catch (Exception e) {
userLocale = Locale.ENGLISH;
}

Map<String, Object> parameters = new HashMap<>();
File resourceBundleDir = new File(CONFIG_PATH + "resourceBundles");
if (resourceBundleDir.exists() && resourceBundleDir.isDirectory()) {
URL[] urls = {resourceBundleDir.toURI().toURL()};
ResourceBundle bundle = loadResourceBundle(userLocale);

if (bundle != null) {
parameters.put(JRParameter.REPORT_RESOURCE_BUNDLE, bundle);
parameters.put(JRParameter.REPORT_LOCALE, userLocale);
}

try (URLClassLoader externalLoader = new URLClassLoader(urls)) {
ResourceBundle externalBundle = ResourceBundle
.getBundle("report_translations", userLocale, externalLoader);
return parameters;
}

/**
* Load translations resource bundle from shared config with fallback to internal translations
* bundle.
*
* @param locale the locale
* @return the resource bundle
*/
private ResourceBundle loadResourceBundle(Locale locale) {
File resourceBundleDir = new File(CONFIG_PATH + "resourceBundles");

parameters.put(JRParameter.REPORT_RESOURCE_BUNDLE, externalBundle);
parameters.put(JRParameter.REPORT_LOCALE, userLocale);
// Attempt to load from the config
if (resourceBundleDir.exists() && resourceBundleDir.isDirectory()) {
try {
URL[] urls = {resourceBundleDir.toURI().toURL()};
try (URLClassLoader externalLoader = new URLClassLoader(urls)) {
return ResourceBundle.getBundle("report_translations", locale, externalLoader);
}
} catch (IOException | MissingResourceException e) {
// No translations bundle
return Collections.emptyMap();
resourceBundleDir = null;
}
}
return parameters;

// Fallback to the internal Classpath
try {
return ResourceBundle.getBundle("resourceBundles/report_translations", locale);
} catch (MissingResourceException e) {
return null;
}
}

/**
Expand Down Expand Up @@ -337,17 +356,31 @@ private Map<String, Object> injectDynamicHeaderParams() throws IOException {
*/
public JasperReport loadReport(JasperTemplate jasperTemplate) throws ReportingException {
if (jasperTemplate != null) {
try (InputStream is = new ByteArrayInputStream(jasperTemplate.getData())) {
return (JasperReport) JRLoader.loadObject(is);
} catch (JRException ex) {
throw new ReportingException(ex, ERROR_REPORTING_FILE_INVALID);
} catch (IOException ex) {
throw new ReportingException(ex, ERROR_REPORTING_IO, ex.getMessage());
}
return loadReport(jasperTemplate.getData());
}
return null;
}

/**
* Load report jasper report.
*
* @param template the template
* @return the jasper report
* @throws ReportingException the reporting exception
*/
public JasperReport loadReport(byte[] template) throws ReportingException {
if (template.length == 0) {
return null;
}
try (InputStream is = new ByteArrayInputStream(template)) {
return (JasperReport) JRLoader.loadObject(is);
} catch (JRException ex) {
throw new ReportingException(ex, ERROR_REPORTING_FILE_INVALID);
} catch (IOException ex) {
throw new ReportingException(ex, ERROR_REPORTING_IO, ex.getMessage());
}
}

/**
* Validate ".jrmxl" file and insert this template to database.
* Throws reporting exception if an error occurs during file validation or parsing,
Expand Down
Loading
Loading