Skip to content

Commit

Permalink
Merge pull request #164 from MeasureAuthoringTool/MAT-6204_ExportBund…
Browse files Browse the repository at this point in the history
…leAsTransaction

Mat 6204 export bundle as transaction
  • Loading branch information
gregory-akins authored Oct 10, 2023
2 parents e59c011 + efc2ee4 commit 4b8fe25
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 72 deletions.
36 changes: 20 additions & 16 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,23 +135,27 @@
<dependency>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
<version>0.6.6</version>
</dependency>
<dependency>
<groupId>gov.cms.madie.packaging</groupId>
<artifactId>packaging-utility</artifactId>
<version>0.2.3</version>
<exclusions>
<exclusion>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
</exclusion>
<exclusion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId>
</exclusion>
</exclusions>
<version>0.6.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
<dependency>
<groupId>gov.cms.madie.packaging</groupId>
<artifactId>packaging-utility</artifactId>
<version>0.2.3</version>
<exclusions>
<exclusion>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
</exclusion>
<exclusion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId>
</exclusion>
</exclusions>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package gov.cms.madie.madiefhirservice.resources;

import gov.cms.madie.madiefhirservice.exceptions.ResourceNotFoundException;
import gov.cms.madie.madiefhirservice.services.TestCaseBundleService;
import gov.cms.madie.madiefhirservice.utils.ExportFileNamesUtil;
import gov.cms.madie.models.dto.ExportDTO;
import gov.cms.madie.models.measure.Measure;
import gov.cms.madie.models.measure.TestCase;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.security.Principal;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.hl7.fhir.r4.model.Bundle;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
Expand All @@ -21,6 +15,16 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import gov.cms.madie.madiefhirservice.exceptions.ResourceNotFoundException;
import gov.cms.madie.madiefhirservice.services.TestCaseBundleService;
import gov.cms.madie.madiefhirservice.utils.ExportFileNamesUtil;
import gov.cms.madie.models.dto.ExportDTO;
import gov.cms.madie.models.measure.Measure;
import gov.cms.madie.models.measure.TestCase;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping(path = "/fhir/test-cases")
Expand All @@ -36,17 +40,19 @@ public class TestCaseBundleController {
public ResponseEntity<byte[]> getTestCaseExportBundle(
Principal principal, @RequestBody ExportDTO exportDTO) {
Measure measure = exportDTO.getMeasure();
List<String> testCaseId = exportDTO.getTestCaseIds();

List<String> testCaseId = exportDTO.getTestCaseIds();
final String username = principal.getName();
log.info(
"User [{}] is attempting to export all test cases from Measure [{}]",
username,
measure.getId());

if (testCaseId == null || testCaseId.isEmpty()) {
throw new ResourceNotFoundException("test cases", "measure", measure.getId());
}
//MAT-6204 Here we're modifying the bundle based on export choice,
// but we don't want to modify it permanently
testCaseBundleService.setExportBundleType(exportDTO, measure);

List<TestCase> testCases =
Optional.ofNullable(measure.getTestCases())
Expand All @@ -55,11 +61,9 @@ public ResponseEntity<byte[]> getTestCaseExportBundle(
.stream()
.filter(tc -> testCaseId.stream().anyMatch(id -> id.equals(tc.getId())))
.collect(Collectors.toList());

Map<String, Bundle> exportableTestCaseBundle =
testCaseBundleService.getTestCaseExportBundle(measure, testCases);
if (testCases.size() != exportableTestCaseBundle.size()) {

// remove the test cases that couldn't be parsed
testCases =
testCases.stream()
Expand All @@ -68,7 +72,6 @@ public ResponseEntity<byte[]> getTestCaseExportBundle(
exportableTestCaseBundle.keySet().stream()
.anyMatch(s -> s.contains(testCase.getPatientId().toString())))
.collect(Collectors.toList());

return ResponseEntity.status(206)
.header(
HttpHeaders.CONTENT_DISPOSITION,
Expand All @@ -80,7 +83,6 @@ public ResponseEntity<byte[]> getTestCaseExportBundle(
testCaseBundleService.zipTestCaseContents(
measure, exportableTestCaseBundle, testCases));
}

return ResponseEntity.ok()
.header(
HttpHeaders.CONTENT_DISPOSITION,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,62 @@
package gov.cms.madie.madiefhirservice.services;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import gov.cms.madie.madiefhirservice.constants.UriConstants;
import gov.cms.madie.madiefhirservice.exceptions.BundleOperationException;
import gov.cms.madie.madiefhirservice.exceptions.InternalServerException;
import gov.cms.madie.madiefhirservice.exceptions.ResourceNotFoundException;
import gov.cms.madie.madiefhirservice.utils.ExportFileNamesUtil;
import gov.cms.madie.madiefhirservice.utils.FhirResourceHelpers;
import gov.cms.madie.models.measure.Measure;
import gov.cms.madie.models.measure.TestCase;
import gov.cms.madie.packaging.utils.PackagingUtility;
import gov.cms.madie.packaging.utils.PackagingUtilityFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;

import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.MarkdownType;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.StringType;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestClientException;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import gov.cms.madie.madiefhirservice.constants.UriConstants;
import gov.cms.madie.madiefhirservice.exceptions.BundleOperationException;
import gov.cms.madie.madiefhirservice.exceptions.InternalServerException;
import gov.cms.madie.madiefhirservice.exceptions.ResourceNotFoundException;
import gov.cms.madie.madiefhirservice.utils.ExportFileNamesUtil;
import gov.cms.madie.madiefhirservice.utils.FhirResourceHelpers;
import gov.cms.madie.models.common.BundleType;
import gov.cms.madie.models.dto.ExportDTO;
import gov.cms.madie.models.measure.Measure;
import gov.cms.madie.models.measure.TestCase;
import gov.cms.madie.packaging.utils.PackagingUtility;
import gov.cms.madie.packaging.utils.PackagingUtilityFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -88,6 +110,41 @@ public Map<String, Bundle> getTestCaseExportBundle(Measure measure, List<TestCas
return testCaseBundle;
}

public void updateEntry(TestCase testCase, BundleType bundleType) {

// Convert Test case JSON to a BUndle
IParser parser =
fhirContext
.newJsonParser()
.setParserErrorHandler(new StrictErrorHandler())
.setPrettyPrint(true);
Bundle bundle = parser.parseResource(Bundle.class, testCase.getJson());

// modify the bundle
org.hl7.fhir.r4.model.Bundle.BundleType fhirBundleType =
org.hl7.fhir.r4.model.Bundle.BundleType.valueOf(bundleType.toString().toUpperCase());
bundle.setType(fhirBundleType);
bundle.setEntry(
bundle.getEntry().stream()
.map(
entry -> {
if (bundleType == BundleType.TRANSACTION) {


FhirResourceHelpers.setResourceEntry(entry.getResource(), entry);
return entry;
} else if (bundleType == BundleType.COLLECTION) {
entry.setRequest(null);
}
return entry;
})
.collect(Collectors.toList()));
// bundle to json
String json = parser.encodeResourceToString(bundle);

testCase.setJson(json);
}

private MeasureReport buildMeasureReport(
TestCase testCase, Measure measure, Bundle testCaseBundle) {
MeasureReport measureReport = new MeasureReport();
Expand Down Expand Up @@ -262,6 +319,26 @@ private String generateReadMe(List<TestCase> testCases) {
return readMe;
}

public void setExportBundleType(ExportDTO exportDTO, Measure measure) {
if (exportDTO.getBundleType() != null) {
BundleType bundleType = BundleType.valueOf(exportDTO.getBundleType().name());
switch (bundleType) {
case COLLECTION:
log.debug("You're exporting a Collection");
// update bundle type for each entry MAT 6405
break;
case TRANSACTION:
log.debug("You're exporting a Transaction");
// update bundle type and add entry.request for each entry
if (measure.getTestCases() != null) {
measure.getTestCases().stream().forEach(testCase -> updateEntry(testCase, bundleType));
}
break;
default:
}
}
}

/**
* Combines the zip from Packaging Utility and a generated ReadMe file for the testcases
*
Expand All @@ -272,32 +349,29 @@ private String generateReadMe(List<TestCase> testCases) {
*/
public byte[] zipTestCaseContents(
Measure measure, Map<String, Bundle> exportableTestCaseBundle, List<TestCase> testCases) {

try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

PackagingUtility utility = PackagingUtilityFactory.getInstance(measure.getModel());
byte[] bytes = utility.getZipBundle(exportableTestCaseBundle, null);

try (ZipOutputStream zos = new ZipOutputStream(baos);
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes))) {
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes)); ) {

// Add the README file to the zip
String readme = generateReadMe(testCases);
ZipEntry entry = new ZipEntry("README.txt");
entry.setSize(readme.length());
zos.putNextEntry(entry);
zos.write(readme.getBytes());

// Add the TestCases back the zip
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
zos.putNextEntry(zipEntry);
zos.write(zis.readAllBytes());
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
zos.closeEntry();
}

// return after the zip streams are closed
return baos.toByteArray();
} catch (RestClientException
Expand All @@ -309,6 +383,7 @@ public byte[] zipTestCaseContents(
| SecurityException
| ClassNotFoundException
| IOException ex) {

log.error("An error occurred while bundling testcases for measure {}", measure.getId(), ex);
throw new BundleOperationException("Measure", measure.getId(), ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ public static Bundle.BundleEntryComponent getBundleEntryComponent(
.setResource(resource);
// for the transaction bundles, add request object to the entry
if ("Transaction".equalsIgnoreCase(bundleType)) {
Bundle.BundleEntryRequestComponent requestComponent =
setResourceEntry(resource, entryComponent);
}
return entryComponent;
}

public static void setResourceEntry(Resource resource, Bundle.BundleEntryComponent entryComponent) {
Bundle.BundleEntryRequestComponent requestComponent =
new Bundle.BundleEntryRequestComponent()
.setMethod(Bundle.HTTPVerb.POST)
.setUrl(resource.getResourceType() + "/" + resource.getIdPart());
entryComponent.setRequest(requestComponent);
}
return entryComponent;
}
}

public static Period getPeriodFromDates(Date startDate, Date endDate) {
return new Period()
Expand Down
Loading

0 comments on commit 4b8fe25

Please sign in to comment.