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
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
package org.openmrs.module.patientdocuments.common;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.openmrs.util.OpenmrsClassLoader;
import org.openmrs.util.OpenmrsUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Helper {

Expand All @@ -37,4 +42,36 @@ public static String getStringFromResource(String resourceName) {
throw new IllegalArgumentException("Unable to load resource: " + resourceName, e);
}
}

public static File getFileFromAppDataDir(String relativePath) {
if (StringUtils.isBlank(relativePath)) {
return null;
}

final File appDataDir = OpenmrsUtil.getApplicationDataDirectoryAsFile();
try {
final Path appDataPath = appDataDir.toPath().toRealPath();
final Path inputPath = Paths.get(relativePath);

if (inputPath.isAbsolute()) {
return null;
}

final Path inputAbsolute = inputPath.toAbsolutePath();
if (!inputAbsolute.equals(inputAbsolute.normalize())) {
return null;
}

final Path resolved = appDataPath.resolve(relativePath).normalize();
final Path resolvedReal = resolved.toRealPath();

if (!resolvedReal.startsWith(appDataPath)) {
return null;
}

return resolvedReal.toFile();
} catch (IllegalArgumentException | IOException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class PatientDocumentsConstants {

public static final String ENCOUNTER_PRINTING_FOOTER_PREFIX = "report.encounterPrinting.footer.";

public static final String ENCOUNTER_PRINTING_STYLESHEET_KEY = "report.encounterPrinting.stylesheet";
public static final String ENCOUNTER_PRINTING_STYLESHEET_PATH_KEY = "report.encounterPrinting.stylesheetPath";

public static final String ENCOUNTER_PRINTING_LOGO_PATH_KEY = "report.encounterPrinting.logopath";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.openmrs.module.reporting.report.ReportRequest;
import org.openmrs.module.reporting.report.renderer.RenderingException;
import org.openmrs.module.reporting.report.renderer.ReportDesignRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.xml.XMLConstants;
Expand All @@ -35,11 +37,14 @@
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URI;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
Expand All @@ -48,6 +53,8 @@
@Handler
public class EncounterPdfReportRenderer extends ReportDesignRenderer {

private static final Logger log = LoggerFactory.getLogger(EncounterPdfReportRenderer.class);

@Override
public String getFilename(ReportRequest request) {
return "EncountersReport.pdf";
Expand Down Expand Up @@ -99,10 +106,11 @@ private void transformXmlToPdf(String xmlData, OutputStream outStream) throws Ex
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, outStream);

String stylesheetName = getStylesheetName();
try (InputStream xslStream = Helper.getInputStreamByResource(stylesheetName)) {
try (InputStream xslStream = getStylesheetStream()) {
if (xslStream == null) {
throw new FileNotFoundException("Stylesheet not found at " + stylesheetName);
throw new FileNotFoundException(
"Default stylesheet not found on classpath: "
+ PatientDocumentsConstants.DEFAULT_ENCOUNTER_FORM_XSL_PATH);
}

TransformerFactory factory = TransformerFactory.newInstance();
Expand All @@ -115,11 +123,26 @@ private void transformXmlToPdf(String xmlData, OutputStream outStream) throws Ex
}
}

private String getStylesheetName() {
String stylesheetName = Context.getService(InitializerService.class).getValueFromKey(PatientDocumentsConstants.ENCOUNTER_PRINTING_STYLESHEET_KEY);
if (StringUtils.isBlank(stylesheetName)) {
stylesheetName = PatientDocumentsConstants.DEFAULT_ENCOUNTER_FORM_XSL_PATH;
InputStream getStylesheetStream() throws IOException {
return getStylesheetStream(getConfiguredStylesheetPath());
}

InputStream getStylesheetStream(String path) throws IOException {
if (StringUtils.isNotBlank(path)) {
File stylesheetFile = Helper.getFileFromAppDataDir(path);
if (stylesheetFile != null && stylesheetFile.isFile() && stylesheetFile.canRead()) {
return Files.newInputStream(stylesheetFile.toPath());
}
log.warn("Configured encounter printing stylesheet '{}' was not found in the OpenMRS "
+ "application data directory; falling back to the default bundled stylesheet.",
path);
}
return stylesheetName;

return Helper.getInputStreamByResource(PatientDocumentsConstants.DEFAULT_ENCOUNTER_FORM_XSL_PATH);
}

private String getConfiguredStylesheetPath() {
return Context.getService(InitializerService.class)
.getValueFromKey(PatientDocumentsConstants.ENCOUNTER_PRINTING_STYLESHEET_PATH_KEY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.openmrs.messagesource.MessageSourceService;
import org.openmrs.module.initializer.api.InitializerService;
import org.openmrs.module.o3forms.api.O3FormsService;
import org.openmrs.module.patientdocuments.common.Helper;
import org.openmrs.module.patientdocuments.common.PatientDocumentsConstants;
import org.openmrs.module.webservices.rest.SimpleObject;
import org.openmrs.util.OpenmrsUtil;
Expand All @@ -34,11 +35,10 @@

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
Expand All @@ -53,6 +53,10 @@ public class EncounterXmlBuilder {

private static final String QUESTIONS_SECTION = "questions";

private static final String ANSWERS_FIELD = "answers";

private static final String CONCEPT_FIELD = "concept";

private static final String LABEL_FIELD = "label";

private InitializerService initializerService;
Expand All @@ -72,8 +76,8 @@ private String getLogoContent() {
return null;
}

File logoFile = resolveSecureLogoPath(logoPath);
if (logoFile == null || !logoFile.exists() || !logoFile.canRead() || !logoFile.isFile()) {
File logoFile = Helper.getFileFromAppDataDir(logoPath);
if (logoFile == null || !logoFile.isFile() || !logoFile.canRead()) {
return null;
}

Expand All @@ -89,40 +93,6 @@ private String getLogoContent() {
return null;
}

private File resolveSecureLogoPath(String logoUrlPath) {
if (StringUtils.isBlank(logoUrlPath)) {
return null;
}

final File appDataDir = OpenmrsUtil.getApplicationDataDirectoryAsFile();
try {
final Path appDataPath = appDataDir.toPath().toRealPath();
final Path logoPath = Paths.get(logoUrlPath);

if (logoPath.isAbsolute()) {
return null;
}

final Path logoAbsolutePath = logoPath.toAbsolutePath();
final Path logoNormalizedPath = logoAbsolutePath.normalize();

if (!logoAbsolutePath.equals(logoNormalizedPath)) {
return null;
}

final Path resolvedLogoPath = appDataPath.resolve(logoUrlPath).normalize();
final Path resolvedLogoRealPath = resolvedLogoPath.toRealPath();

if (!resolvedLogoRealPath.startsWith(appDataPath)) {
return null;
}

return resolvedLogoRealPath.toFile();
} catch (IllegalArgumentException | IOException e) {
return null;
}
}

public String build(EncounterPrintingContext printingContext) {
StringBuilder xml = new StringBuilder();
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
Expand Down Expand Up @@ -399,7 +369,19 @@ private String extractConceptRef(Map<String, Object> question) {
return null;
}
Map<String, Object> options = (Map<String, Object>) question.get(QUESTION_OPTIONS_SECTION);
return (String) options.get("concept");
return (String) options.get(CONCEPT_FIELD);
}

private List<Map<String, Object>> extractAnswers(Map<String, Object> question) {
Object optionsObj = question.get(QUESTION_OPTIONS_SECTION);
if (!(optionsObj instanceof Map)) {
return Collections.emptyList();
}
Object answersObj = ((Map<String, Object>) optionsObj).get(ANSWERS_FIELD);
if (answersObj instanceof List) {
return (List<Map<String, Object>>) answersObj;
}
return Collections.emptyList();
}

private String formatValueAsText(Object value) {
Expand All @@ -426,16 +408,21 @@ private String findObsValue(Map<String, Object> question, Map<String, List<Obs>>
}

return observations.stream()
.map(obs -> getLocalizedObsValue(obs, locale))
.map(obs -> getLocalizedObsValue(question, obs, locale))
.collect(Collectors.joining(", "));
}

private String escape(String input) {
return StringEscapeUtils.escapeXml10(StringUtils.defaultString(input));
}

private String getLocalizedObsValue(Obs obs, Locale locale) {
private String getLocalizedObsValue(Map<String, Object> question, Obs obs, Locale locale) {
if (obs.getValueCoded() != null) {
String answerLabel = findAnswerLabel(question, obs.getValueCoded().getUuid(), locale);
if (StringUtils.isNotBlank(answerLabel)) {
return answerLabel;
}

ConceptName localizedName = obs.getValueCoded().getName(locale);
if (localizedName != null) {
return localizedName.getName();
Expand All @@ -446,17 +433,29 @@ private String getLocalizedObsValue(Obs obs, Locale locale) {
return obs.getValueAsString(locale);
}

private String getLocalizedLabel(String defaultLabel, String conceptRef, Locale locale) {
if (StringUtils.isNotBlank(conceptRef)) {
Concept concept = getConceptService().getConceptByReference(conceptRef);
if (concept != null) {
ConceptName localizedName = concept.getName(locale);
if (localizedName != null) {
return localizedName.getName();
}
private String findAnswerLabel(Map<String, Object> question, String obsConceptUuid, Locale locale) {
for (Map<String, Object> answer : extractAnswers(question)) {
String answerConceptRef = (String) answer.get(CONCEPT_FIELD);
if (StringUtils.isBlank(answerConceptRef)) {
continue;
}
if (answerMatchesConcept(answerConceptRef, obsConceptUuid)) {
String answerLabel = (String) answer.get(LABEL_FIELD);
return StringUtils.isNotBlank(answerLabel) ? getLocalizedLabel(answerLabel, null, locale) : null;
}
}
return null;
}

private boolean answerMatchesConcept(String answerConceptRef, String obsConceptUuid) {
if (answerConceptRef.equals(obsConceptUuid)) {
return true;
}
Concept answerConcept = getConceptService().getConceptByReference(answerConceptRef);
return answerConcept != null && answerConcept.getUuid().equals(obsConceptUuid);
}

private String getLocalizedLabel(String defaultLabel, String conceptRef, Locale locale) {
if (StringUtils.isNotBlank(defaultLabel)) {
try {
String translated = getMessageSourceService().getMessage(defaultLabel, null, locale);
Expand All @@ -465,9 +464,20 @@ private String getLocalizedLabel(String defaultLabel, String conceptRef, Locale
}
} catch (Exception ignored) {
}
return defaultLabel;
}

if (StringUtils.isNotBlank(conceptRef)) {
Concept concept = getConceptService().getConceptByReference(conceptRef);
if (concept != null) {
ConceptName localizedName = concept.getName(locale);
if (localizedName != null) {
return localizedName.getName();
}
}
}

return defaultLabel;
return "";
}

private MessageSourceService getMessageSourceService() {
Expand Down
11 changes: 7 additions & 4 deletions api/src/main/resources/defaultEncounterFormFopStylesheet.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,23 @@
</xsl:template>

<xsl:template match="page">
<fo:block keep-together.within-page="always" margin-bottom="5mm">
<fo:block margin-bottom="5mm">
<xsl:if test="@label">
<fo:block font-size="13pt" font-weight="bold" color="#005f87" margin-top="4mm" margin-bottom="2mm" border-bottom="0.5pt solid #005f87">
<fo:block font-size="13pt" font-weight="bold" color="#005f87" margin-top="4mm" margin-bottom="2mm"
border-bottom="0.5pt solid #005f87" keep-with-next.within-page="always">
<xsl:value-of select="@label"/>
</fo:block>
</xsl:if>

<xsl:apply-templates select="section"/> </fo:block>
<xsl:apply-templates select="section"/>
</fo:block>
</xsl:template>

<xsl:template match="section">
<fo:block margin-left="2mm" margin-bottom="4mm">
<xsl:if test="@label != ''">
<fo:block font-size="11pt" font-weight="bold" background-color="#eef" padding="1mm" margin-bottom="2mm">
<fo:block font-size="11pt" font-weight="bold" background-color="#eef" padding="1mm" margin-bottom="2mm"
keep-with-next.within-page="always">
<xsl:value-of select="@label"/>
</fo:block>
</xsl:if>
Expand Down
Loading