From 2d81bc9dc0b38317dc4058c6a97222515f417e93 Mon Sep 17 00:00:00 2001 From: JP Date: Thu, 25 Jan 2024 15:42:15 -0700 Subject: [PATCH] Add a CQL export option to the StripGeneratedContent operation (#511) * Add CQL export to StripContent operation * More cleanup / refactoring of the strip content processor * Add suppression for errorneous warning * Cleanup and a test * Fix coment * Fix parameter name * Further clean-up * Swap to utility functions --- .vscode/settings.json | 3 + .../StripGeneratedContentOperation.java | 539 +----------------- .../stripcontent/BaseContentStripper.java | 201 +++++++ .../stripcontent/ContentStripper.java | 8 + .../stripcontent/ContentStripperDstu3.java | 11 + .../stripcontent/ContentStripperOptions.java | 62 ++ .../stripcontent/ContentStripperR4.java | 11 + .../stripcontent/ContentStripperR5.java | 12 + .../stripcontent/StripContentExecutor.java | 82 +++ .../stripcontent/StripContentParams.java | 45 ++ .../StripGeneratedContentOperationTest.java | 52 +- .../LibraryBreastCancerScreeningFHIR.json | 4 - 12 files changed, 479 insertions(+), 551 deletions(-) create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/BaseContentStripper.java create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripper.java create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperDstu3.java create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperOptions.java create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR4.java create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR5.java create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentExecutor.java create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentParams.java diff --git a/.vscode/settings.json b/.vscode/settings.json index 5ab17bf37..3f86936a0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,11 +8,14 @@ "conceptmap", "Cqfm", "cqloptions", + "DEPENDSON", + "Dstu", "Fhir", "opencds", "pagecontent", "plandefinition", "qicore", + "stripcontent", "testng" ], "java.compile.nullAnalysis.mode": "automatic", diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperation.java b/tooling/src/main/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperation.java index 4939a2895..8ac2b52e6 100644 --- a/tooling/src/main/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperation.java +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperation.java @@ -1,39 +1,13 @@ package org.opencds.cqf.tooling.operation; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Attachment; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.Library; -import org.hl7.fhir.r4.model.Measure; -import org.hl7.fhir.r4.model.Narrative; -import org.hl7.fhir.r4.model.PlanDefinition; -import org.hl7.fhir.r4.model.Questionnaire; -import org.hl7.fhir.r4.model.RelatedArtifact; import org.opencds.cqf.tooling.Operation; - -import ca.uhn.fhir.context.FhirContext; +import org.opencds.cqf.tooling.operations.stripcontent.StripContentParams; +import org.opencds.cqf.tooling.operations.stripcontent.StripContentExecutor; public class StripGeneratedContentOperation extends Operation { - - private String pathToResource; - private FhirContext context; - String version; - @Override public void execute(String[] args) { + var params = new StripContentParams(); for (String arg : args) { if (arg.equals("-StripGeneratedContent")) continue; String[] flagAndValue = arg.split("="); @@ -46,513 +20,24 @@ public void execute(String[] args) { switch (flag.replace("-", "").toLowerCase()) { case "outputpath": case "op": - setOutputPath(value); + params.outputDirectory(value); break; case "pathtores": case "ptr": - pathToResource = value; + params.inputDirectory(value); break; case "version": case "v": - version = value; + params.fhirVersion(value); break; - default: - throw new IllegalArgumentException("Unknown flag: " + flag); - } - } - this.context = contextForVersion(version); - File res = validateDirectory(pathToResource); - var files = getListOfActionableFiles(res); - - for(File file : files) { - parseAndStripResource(file); - } - - } - - private File validateDirectory(String pathToDir) { - if (pathToDir == null) { - throw new IllegalArgumentException("The path to the directory is required"); - } - - File bundleDirectory = new File(pathToDir); - if (!bundleDirectory.isDirectory()) { - throw new IllegalArgumentException("The path supplied is not a directory"); - } - return bundleDirectory; - } - private Collection getListOfActionableFiles(File file) { - return FileUtils.listFiles(file, new String[] { "json", "xml"}, true); - } - - private FhirContext contextForVersion(String version) { - if (StringUtils.isEmpty(version)) { - return FhirContext.forR4Cached(); - } else { - switch (version.toLowerCase()) { - case "dstu3": - return FhirContext.forDstu3Cached(); - case "r4": - return FhirContext.forR4Cached(); - case "r5": - return FhirContext.forR5Cached(); + case "cql": + params.cqlExportDirectory(value); + break; default: - throw new IllegalArgumentException("Unknown fhir version: " + version); - } - } - } - - private void parseAndStripResource(File file) { - IBaseResource theResource = null; - try { - if (file.getName().endsWith(".json")) { - theResource = context.newJsonParser().parseResource(new FileReader(file)); - } else if(file.getName().endsWith(".xml")){ - theResource = context.newXmlParser().parseResource(new FileReader(file)); - } - - if (theResource == null) { - throw new RuntimeException(String.format("failed to parse resource for file: %s", file.toString())); - } - - stripResource(file.getPath().substring(pathToResource.length()), theResource); - } catch (FileNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException(e.getMessage()); - } - catch (Exception e) { - e.printStackTrace(); - String message = String.format("'%s' will not be included in the resource because the following error occurred: '%s'", file.getName(), e.getMessage()); - System.out.println(message); - } - } - - private void stripResource(String fileName, IBaseResource resource) { - switch(context.getVersion().getVersion().name().toLowerCase()) { - case "dstu3": - if(resource.getIdElement().getResourceType().equals("Library")) { - org.hl7.fhir.dstu3.model.Library library = (org.hl7.fhir.dstu3.model.Library) resource; - stripLibraryAndWrite(fileName, library); - } else if (resource.getIdElement().getResourceType().equals("Measure")) { - org.hl7.fhir.dstu3.model.Measure measure = (org.hl7.fhir.dstu3.model.Measure) resource; - stripMeasureAndWrite(fileName, measure); - } else if (resource.getIdElement().getResourceType().equals("PlanDefinition")) { - org.hl7.fhir.dstu3.model.PlanDefinition planDefinition = (org.hl7.fhir.dstu3.model.PlanDefinition) resource; - stripPlanDefinitionAndWrite(fileName, planDefinition); - } else if (resource.getIdElement().getResourceType().equals("Questionnaire")) { - org.hl7.fhir.dstu3.model.Questionnaire questionnaire = (org.hl7.fhir.dstu3.model.Questionnaire) resource; - stripQuestionnaireAndWrite(fileName, questionnaire); - } else if (resource instanceof org.hl7.fhir.dstu3.model.DomainResource) { - stripResourceAndWrite(fileName, (org.hl7.fhir.dstu3.model.DomainResource)resource); - } - break; - case "r4": - if(resource.getIdElement().getResourceType().equals("Library")) { - Library library = (Library) resource; - stripLibraryAndWrite(fileName, library); - } else if (resource.getIdElement().getResourceType().equals("Measure")) { - Measure measure = (Measure) resource; - stripMeasureAndWrite(fileName, measure); - } else if (resource.getIdElement().getResourceType().equals("PlanDefinition")) { - PlanDefinition planDefinition = (PlanDefinition) resource; - stripPlanDefinitionAndWrite(fileName, planDefinition); - } else if (resource.getIdElement().getResourceType().equals("Questionnaire")) { - Questionnaire questionnaire = (Questionnaire) resource; - stripQuestionnaireAndWrite(fileName, questionnaire); - } else if (resource instanceof org.hl7.fhir.r4.model.DomainResource) { - stripResourceAndWrite(fileName, (org.hl7.fhir.r4.model.DomainResource)resource); - } - break; - case "r5": - if(resource.getIdElement().getResourceType().equals("Library")) { - org.hl7.fhir.r5.model.Library library = (org.hl7.fhir.r5.model.Library) resource; - stripLibraryAndWrite(fileName, library); - } else if (resource.getIdElement().getResourceType().equals("Measure")) { - org.hl7.fhir.r5.model.Measure measure = (org.hl7.fhir.r5.model.Measure) resource; - stripMeasureAndWrite(fileName, measure); - } else if (resource.getIdElement().getResourceType().equals("PlanDefinition")) { - org.hl7.fhir.r5.model.PlanDefinition planDefinition = (org.hl7.fhir.r5.model.PlanDefinition) resource; - stripPlanDefinitionAndWrite(fileName, planDefinition); - } else if (resource.getIdElement().getResourceType().equals("Questionnaire")) { - org.hl7.fhir.r5.model.Questionnaire questionnaire = (org.hl7.fhir.r5.model.Questionnaire) resource; - stripQuestionnaireAndWrite(fileName, questionnaire); - } else if (resource instanceof org.hl7.fhir.r5.model.DomainResource) { - stripResourceAndWrite(fileName, (org.hl7.fhir.r5.model.DomainResource)resource); - } - break; - - } - - } - - private void stripResourceAndWrite(String fileName, org.hl7.fhir.dstu3.model.DomainResource resource) { - resource.setText(null); - writeFile(fileName, resource); - } - - private void stripResourceAndWrite(String fileName, org.hl7.fhir.r4.model.DomainResource resource) { - resource.setText(null); - writeFile(fileName, resource); - } - - private void stripResourceAndWrite(String fileName, org.hl7.fhir.r5.model.DomainResource resource) { - resource.setText(null); - writeFile(fileName, resource); - } - - private void stripLibraryAndWrite(String fileName, Library library) { - if(library.hasText()) { - library.setText(new Narrative()); - } - if(library.hasParameter()) { - library.setParameter(Collections.emptyList()); - } - if(library.hasDataRequirement()) { - library.setDataRequirement(Collections.emptyList()); - } - if (library.hasRelatedArtifact()) { - List list = library.getRelatedArtifact() - .stream() - .filter(relatedArtifact -> (relatedArtifact.hasType() && - relatedArtifact.getType() != RelatedArtifact.RelatedArtifactType.DEPENDSON)) - .collect(Collectors.toList()); - library.setRelatedArtifact(list); - } - - if(library.hasExtension()) { - List list = library.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - library.setExtension(list); - } - if(library.hasContent()) { - List attachments = - library.getContent() - .stream() - .filter(attachment -> - !(attachment.getContentType().equalsIgnoreCase("application/elm+xml")) - && !(attachment.getContentType().equalsIgnoreCase("application/elm+json")) - && !(attachment.getContentType().equalsIgnoreCase("text/cql") && - (StringUtils.isEmpty(attachment.getUrl()) && attachment.getData() != null))) - .collect(Collectors.toList()); - library.setContent(attachments); - } - writeFile(fileName, library); - - } - - private void stripMeasureAndWrite(String fileName, Measure measure) { - if(measure.hasText()) { - measure.setText(new Narrative()); - } - if(measure.hasExtension()) { - List list = measure.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - measure.setExtension(list); - } - - if(measure.hasRelatedArtifact()) { - measure.setRelatedArtifact(Collections.emptyList()); - } - writeFile(fileName, measure); - } - - private void stripPlanDefinitionAndWrite(String fileName, PlanDefinition planDefinition) { - if(planDefinition.hasText()) { - planDefinition.setText(new Narrative()); - } - - if(planDefinition.hasRelatedArtifact()) { - planDefinition.setRelatedArtifact(Collections.emptyList()); - } - - if(planDefinition.hasExtension()) { - List list = planDefinition.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - planDefinition.setExtension(list); - } - writeFile(fileName, planDefinition); - } - - private void stripQuestionnaireAndWrite(String fileName, Questionnaire questionnaire) { - if(questionnaire.hasText()) { - questionnaire.setText(new Narrative()); - } - - - if(questionnaire.hasExtension()) { - List list = questionnaire.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - questionnaire.setExtension(list); - } - writeFile(fileName, questionnaire); - } - - private void stripLibraryAndWrite(String fileName, org.hl7.fhir.dstu3.model.Library library) { - if(library.hasText()) { - library.setText(new org.hl7.fhir.dstu3.model.Narrative()); - } - if(library.hasParameter()) { - library.setParameter(Collections.emptyList()); - } - if(library.hasDataRequirement()) { - library.setDataRequirement(Collections.emptyList()); - } - if (library.hasRelatedArtifact()) { - List list = library.getRelatedArtifact() - .stream() - .filter(relatedArtifact -> (relatedArtifact.hasType() && - !(relatedArtifact.getType()== org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.DEPENDSON))) - .collect(Collectors.toList()); - library.setRelatedArtifact(list); - } - - if(library.hasExtension()) { - List list = library.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - library.setExtension(list); - } - if(library.hasContent()) { - List attachments = - library.getContent() - .stream() - .filter(attachment -> - !(attachment.getContentType().equalsIgnoreCase("application/elm+xml")) - && !(attachment.getContentType().equalsIgnoreCase("application/elm+json"))) - .collect(Collectors.toList()); - library.setContent(attachments); - } - writeFile(fileName, library); - - } - - private void stripMeasureAndWrite(String fileName, org.hl7.fhir.dstu3.model.Measure measure) { - if(measure.hasText()) { - measure.setText(new org.hl7.fhir.dstu3.model.Narrative()); - } - if(measure.hasExtension()) { - List list = measure.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - measure.setExtension(list); - } - - if(measure.hasRelatedArtifact()) { - measure.setRelatedArtifact(Collections.emptyList()); - } - writeFile(fileName, measure); - } - - private void stripPlanDefinitionAndWrite(String fileName, org.hl7.fhir.dstu3.model.PlanDefinition planDefinition) { - if(planDefinition.hasText()) { - planDefinition.setText(new org.hl7.fhir.dstu3.model.Narrative()); - } - - if(planDefinition.hasRelatedArtifact()) { - planDefinition.setRelatedArtifact(Collections.emptyList()); - } - - if(planDefinition.hasExtension()) { - List list = planDefinition.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - planDefinition.setExtension(list); - } - writeFile(fileName, planDefinition); - } - - private void stripQuestionnaireAndWrite(String fileName, org.hl7.fhir.dstu3.model.Questionnaire questionnaire) { - if(questionnaire.hasText()) { - questionnaire.setText(new org.hl7.fhir.dstu3.model.Narrative()); - } - - - if(questionnaire.hasExtension()) { - List list = questionnaire.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - questionnaire.setExtension(list); - } - writeFile(fileName, questionnaire); - } - - private void stripLibraryAndWrite(String fileName, org.hl7.fhir.r5.model.Library library) { - if(library.hasText()) { - library.setText(new org.hl7.fhir.r5.model.Narrative()); - } - if(library.hasParameter()) { - library.setParameter(Collections.emptyList()); - } - if(library.hasDataRequirement()) { - library.setDataRequirement(Collections.emptyList()); - } - if (library.hasRelatedArtifact()) { - List list = library.getRelatedArtifact() - .stream() - .filter(relatedArtifact -> (relatedArtifact.hasType() && - !(relatedArtifact.getType() == org.hl7.fhir.r5.model.RelatedArtifact.RelatedArtifactType.DEPENDSON))) - .collect(Collectors.toList()); - library.setRelatedArtifact(list); - } - - if(library.hasExtension()) { - List list = library.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - library.setExtension(list); - } - if(library.hasContent()) { - List attachments = - library.getContent() - .stream() - .filter(attachment -> - !(attachment.getContentType().equalsIgnoreCase("application/elm+xml")) - && !(attachment.getContentType().equalsIgnoreCase("application/elm+json"))) - .collect(Collectors.toList()); - library.setContent(attachments); - } - writeFile(fileName, library); - - } - - private void stripMeasureAndWrite(String fileName, org.hl7.fhir.r5.model.Measure measure) { - if(measure.hasText()) { - measure.setText(new org.hl7.fhir.r5.model.Narrative()); - } - if(measure.hasExtension()) { - List list = measure.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - measure.setExtension(list); - } - - if(measure.hasRelatedArtifact()) { - measure.setRelatedArtifact(Collections.emptyList()); - } - writeFile(fileName, measure); - } - - private void stripPlanDefinitionAndWrite(String fileName, org.hl7.fhir.r5.model.PlanDefinition planDefinition) { - if(planDefinition.hasText()) { - planDefinition.setText(new org.hl7.fhir.r5.model.Narrative()); - } - - if(planDefinition.hasRelatedArtifact()) { - planDefinition.setRelatedArtifact(Collections.emptyList()); - } - - if(planDefinition.hasExtension()) { - List list = planDefinition.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - planDefinition.setExtension(list); - } - writeFile(fileName, planDefinition); - } - - private void stripQuestionnaireAndWrite(String fileName, org.hl7.fhir.r5.model.Questionnaire questionnaire) { - if(questionnaire.hasText()) { - questionnaire.setText(new org.hl7.fhir.r5.model.Narrative()); - } - - - if(questionnaire.hasExtension()) { - List list = questionnaire.getExtension() - .stream() - .filter(extension -> - !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement")) - && !(extension.hasUrl() && extension.getUrl().equals("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"))) - .collect(Collectors.toList()); - questionnaire.setExtension(list); - } - writeFile(fileName, questionnaire); - } - - - private void writeFile(String fileName, IBaseResource resource) { - String output = ""; - if (fileName.endsWith(".json")) { - output = context.newJsonParser().setPrettyPrint(true).encodeResourceToString(resource); - } else if (fileName.endsWith(".xml")) { - output = context.newXmlParser().setPrettyPrint(true).encodeResourceToString(resource); - } - BufferedWriter writer; - String outFileName = String.format("%s%s", getOutputPath(), fileName); - try { - File f = new File(outFileName); - if (!f.getParentFile().exists()) { - f.getParentFile().mkdirs(); - } - if (!f.exists()) { - f.createNewFile(); + throw new IllegalArgumentException("Unknown flag: " + flag); } - writer = new BufferedWriter(new FileWriter(outFileName)); - writer.write(output); - writer.flush(); - writer.close(); - } catch (IOException e) { - throw new RuntimeException(e); } + + new StripContentExecutor(params).execute(); } } diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/BaseContentStripper.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/BaseContentStripper.java new file mode 100644 index 000000000..09689924f --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/BaseContentStripper.java @@ -0,0 +1,201 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +import static org.opencds.cqf.tooling.utilities.converters.ResourceAndTypeConverter.convertFromR5Resource; +import static org.opencds.cqf.tooling.utilities.converters.ResourceAndTypeConverter.convertToR5Resource; + +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r5.model.Attachment; +import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.Library; +import org.hl7.fhir.r5.model.Measure; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.PlanDefinition; +import org.hl7.fhir.r5.model.Questionnaire; +import org.hl7.fhir.r5.model.RelatedArtifact; +import org.hl7.fhir.r5.model.Resource; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IParser; + +/** + * This class is used to strip autogenerated content from FHIR resources. This includes narrative, + * extensions added by the tooling, related artifacts that are auto detected from the CQL, + * contained resources added by the tooling, and ELM generated from the CQL. + * + * This class converts the resource to its R5 equivalent, strips the content, and then converts + * back to the original FHIR version. + * + * The T parameter is used to specify the version of the Resource base class to use for the operation + * and conversions. + */ +abstract class BaseContentStripper implements ContentStripper { + protected abstract FhirContext context(); + + public void stripFile(File inputFile, File outputFile, ContentStripperOptions options) { + var resource = parseResource(inputFile); + + var upgraded = convertToR5Resource(context(), resource); + stripResource(upgraded, outputFile, options); + + @SuppressWarnings("unchecked") + var downgraded = (T) convertFromR5Resource(context(), upgraded); + writeResource(outputFile, downgraded); + } + + protected void writeContent(File f, String content) { + if (!f.getParentFile().exists()) { + f.getParentFile().mkdirs(); + } + + try (var writer = new BufferedWriter(new FileWriter(f))) { + writer.write(content); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected IParser parserForFile(File file) { + if (file.getName().endsWith(".json")) { + return context().newJsonParser(); + } else if (file.getName().endsWith(".xml")) { + return context().newXmlParser(); + } else { + throw new IllegalArgumentException(String.format("unsupported file type: %s", file.getName())); + } + } + + protected IBaseResource parseResource(File file) { + var parser = parserForFile(file); + try (var reader = new FileReader(file)) { + return parser.parseResource(reader); + } + catch (IOException | DataFormatException e) { + throw new RuntimeException(String.format("Error parsing file %s", file.getName()), e); + } + } + + protected void writeResource(File file, IBaseResource resource) { + var parser = parserForFile(file).setPrettyPrint(true); + var output = parser.encodeResourceToString(resource); + writeContent(file, output); + } + + // Output file is required because the CQL export functionality requires knowledge of the library + // file location to correctly set the Library.content.url property. + private Resource stripResource(IBaseResource resource, File outputFile, ContentStripperOptions options) { + switch (resource.fhirType()) { + case "Library": + return stripLibrary((Library) resource, outputFile, options); + case "Measure": + return stripMeasure((Measure) resource, options); + case "PlanDefinition": + return stripPlanDefinition((PlanDefinition) resource, options); + case "Questionnaire": + return stripQuestionnaire((Questionnaire) resource, options); + default: + return stripResource((DomainResource) resource, options); + } + } + + private boolean isCqlOptionsParameters(Resource resource) { + if (!(resource instanceof Parameters)) { + return false; + } + + var parameters = (Parameters) resource; + return "options".equals(parameters.getId()); + } + + private void filterContained(List contained) { + contained.removeIf(this::isCqlOptionsParameters); + } + + private void filterExtensions(List extensions, Set strippedExtensions) { + extensions.removeIf(x -> strippedExtensions.contains(x.getUrl())); + } + + private void filterContent(List attachments, Set strippedContentTypes) { + attachments.removeIf(x -> strippedContentTypes.contains(x.getContentType())); + } + + private void filterRelatedArtifacts(List artifacts) { + artifacts.removeIf(x -> RelatedArtifact.RelatedArtifactType.DEPENDSON.equals(x.getType())); + } + + // Strip library includes functionality to export the cql file, + // so it requires knowledge of the target directory for the Library. + private Library stripLibrary(Library library, File libraryFile, ContentStripperOptions options) { + stripResource(library, options); + library.setParameter(null); + library.setDataRequirement(null); + filterRelatedArtifacts(library.getRelatedArtifact()); + filterContent(library.getContent(), options.strippedContentTypes()); + exportCql(library.getContent(), library.getName(), libraryFile, options.cqlExportDirectory()); + return library; + } + + private Measure stripMeasure(Measure measure, ContentStripperOptions options) { + stripResource(measure, options); + filterRelatedArtifacts(measure.getRelatedArtifact()); + return measure; + } + + private PlanDefinition stripPlanDefinition(PlanDefinition planDefinition, ContentStripperOptions options) { + stripResource(planDefinition, options); + filterRelatedArtifacts(planDefinition.getRelatedArtifact()); + return planDefinition; + } + + private Questionnaire stripQuestionnaire(Questionnaire questionnaire, ContentStripperOptions options) { + stripResource(questionnaire, options); + filterRelatedArtifacts(questionnaire.getRelatedArtifact()); + return questionnaire; + } + + private DomainResource stripResource(DomainResource resource, ContentStripperOptions options) { + resource.setText(null); + filterExtensions(resource.getExtension(), options.strippedExtensionUrls()); + filterContained(resource.getContained()); + return resource; + } + + private void exportCql(Attachment content, String libraryName, File libraryFile, File cqlExportDirectory) { + checkNotNull(libraryName, "libraryName must be provided"); + if (content.getData() == null || cqlExportDirectory == null) { + return; + } + + // CQL content is encoded as base64, so we need to decode it + // to get back to the original CQL. + var base64 = content.getDataElement().getValueAsString(); + var cql = new String(java.util.Base64.getDecoder().decode(base64)); + + var cqlFileName = libraryName + ".cql"; + var cqlFile = cqlExportDirectory.toPath().resolve(cqlFileName).toFile(); + + content.setUrl(libraryFile.toPath().relativize(cqlFile.toPath()).toString()); + content.setDataElement(null); + writeContent(cqlFile, cql); + } + + private void exportCql(List content, String libraryName, File libraryOutputFile, File cqlExportDirectory) { + for (Attachment attachment : content) { + if (ContentStripperOptions.CQL_CONTENT_TYPE.equals(attachment.getContentType())) { + exportCql(attachment, libraryName, libraryOutputFile, cqlExportDirectory); + } + } + } +} diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripper.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripper.java new file mode 100644 index 000000000..9db8b6e31 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripper.java @@ -0,0 +1,8 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +import java.io.File; + +// Intentionally package-private. This is a package-internal API for ContentStripper +interface ContentStripper { + void stripFile(File inputPath, File outputPath, ContentStripperOptions options); +} \ No newline at end of file diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperDstu3.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperDstu3.java new file mode 100644 index 000000000..c2ae95c02 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperDstu3.java @@ -0,0 +1,11 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +import org.hl7.fhir.dstu3.model.Resource; +import ca.uhn.fhir.context.FhirContext; + +class ContentStripperDstu3 extends BaseContentStripper { + @Override + protected FhirContext context() { + return FhirContext.forDstu3Cached(); + } +} \ No newline at end of file diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperOptions.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperOptions.java new file mode 100644 index 000000000..08ffcd559 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperOptions.java @@ -0,0 +1,62 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +// Intentionally package-private. This is a package-internal API for ContentStripper +class ContentStripperOptions { + static final String CQL_CONTENT_TYPE = "text/cql"; + static final String ELM_JSON_CONTENT_TYPE = "application/elm+json"; + static final String ELM_XML_CONTENT_TYPE = "application/elm+xml"; + + static final Set DEFAULT_STRIPPED_CONTENT_TYPES = new HashSet<>( + Arrays.asList(ELM_JSON_CONTENT_TYPE, ELM_XML_CONTENT_TYPE)); + + static final Set DEFAULT_STRIPPED_EXTENSION_URLS = new HashSet<>( + Arrays.asList("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter", + "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement", + "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition", + "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem", + "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", + "http://hl7.org/fhir/StructureDefinition/cqf-cqlOptions")); + + private ContentStripperOptions() { + // Intentionally empty, forces use of the static factory + } + + public static ContentStripperOptions defaultOptions() { + return new ContentStripperOptions(); + } + + private File cqlExportDirectory; + public File cqlExportDirectory() { + return cqlExportDirectory; + } + public ContentStripperOptions cqlExportDirectory(File cqlExportDirectory) { + this.cqlExportDirectory = cqlExportDirectory; + return this; + } + + private Set strippedContentTypes = DEFAULT_STRIPPED_CONTENT_TYPES; + public Set strippedContentTypes() { + return this.strippedContentTypes; + } + + public ContentStripperOptions strippedContentTypes(Set strippedContentTypes) { + this.strippedContentTypes = strippedContentTypes; + return this; + } + + private Set strippedExtensionUrls = DEFAULT_STRIPPED_EXTENSION_URLS; + public Set strippedExtensionUrls() { + return this.strippedExtensionUrls; + } + + public ContentStripperOptions strippedExtensionUrls(Set strippedExtensionUrls) { + this.strippedExtensionUrls = strippedExtensionUrls; + return this; + } + +} \ No newline at end of file diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR4.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR4.java new file mode 100644 index 000000000..5cbcda706 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR4.java @@ -0,0 +1,11 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +import org.hl7.fhir.r4.model.Resource; +import ca.uhn.fhir.context.FhirContext; + +class ContentStripperR4 extends BaseContentStripper { + @Override + protected FhirContext context() { + return FhirContext.forR4Cached(); + } +} \ No newline at end of file diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR5.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR5.java new file mode 100644 index 000000000..d2e94c312 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/ContentStripperR5.java @@ -0,0 +1,12 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +import org.hl7.fhir.r5.model.Resource; + +import ca.uhn.fhir.context.FhirContext; + +class ContentStripperR5 extends BaseContentStripper { + @Override + protected FhirContext context() { + return FhirContext.forR5Cached(); + } +} \ No newline at end of file diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentExecutor.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentExecutor.java new file mode 100644 index 000000000..656aefdc7 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentExecutor.java @@ -0,0 +1,82 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +import java.io.File; +import java.util.Collection; + +import org.apache.commons.io.FileUtils; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkArgument; +import ca.uhn.fhir.context.FhirVersionEnum; + +/** + * This class executes the StripContent command line operation. It picks the correct version + * of a ContentStripper based on the FHIR version specified in the command line arguments. It then + * iterates over all the files in the input directory and runs the stripFile method on each file. + */ +public class StripContentExecutor { + + private FhirVersionEnum versionEnum; + private File inputDirectory; + private File outputDirectory; + private String cqlExportDirectory; + + public StripContentExecutor(StripContentParams params) { + checkNotNull(params, "params must be provided"); + checkArgument(params.inputDirectory() != null, "inputDirectory must be provided"); + checkArgument(params.outputDirectory() != null, "outputDirectory must be provided"); + this.versionEnum = versionForString(params.fhirVersion()); + this.inputDirectory = validateDirectory(params.inputDirectory()); + this.outputDirectory = new File(params.outputDirectory()); + this.cqlExportDirectory = params.cqlExportDirectory(); + } + + public void execute() { + var files = listResourceFiles(inputDirectory); + var contentStripper = createContentStripper(); + var options = createContentStripperOptions(); + for (File file : files) { + // Keep the same filename, but change the directory to the output directory + var outputFile = outputDirectory.toPath().resolve(file.getName()).toFile(); + contentStripper.stripFile(file, outputFile, options); + } + } + + private ContentStripperOptions createContentStripperOptions() { + var cqlExportFile = this.cqlExportDirectory != null ? new File(this.cqlExportDirectory) : null; + return ContentStripperOptions.defaultOptions().cqlExportDirectory(cqlExportFile); + } + + private ContentStripper createContentStripper() { + switch (versionEnum) { + case DSTU3: + return new ContentStripperDstu3(); + case R4: + return new ContentStripperR4(); + case R5: + return new ContentStripperR5(); + default: + throw new IllegalArgumentException("Unsupported FHIR version"); + } + } + + private File validateDirectory(String pathToDir) { + checkNotNull(pathToDir, "The path to the directory is required"); + File directory = new File(pathToDir); + if (!directory.isDirectory()) { + throw new IllegalArgumentException("The path supplied is not a directory"); + } + return directory; + } + + private Collection listResourceFiles(File file) { + return FileUtils.listFiles(file, new String[] { "json", "xml"}, true); + } + + private FhirVersionEnum versionForString(String version) { + if (version == null) { + return FhirVersionEnum.R4; + } + return FhirVersionEnum.forVersionString(version.toUpperCase()); + } +} diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentParams.java b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentParams.java new file mode 100644 index 000000000..8f734aa79 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operations/stripcontent/StripContentParams.java @@ -0,0 +1,45 @@ +package org.opencds.cqf.tooling.operations.stripcontent; + +public class StripContentParams { + + private String inputDirectory; + private String outputDirectory; + private String fhirVersion; + private String cqlExportDirectory; + + public String inputDirectory() { + return inputDirectory; + } + + public StripContentParams inputDirectory(String inputDirectory) { + this.inputDirectory = inputDirectory; + return this; + } + + public String outputDirectory() { + return outputDirectory; + } + + public StripContentParams outputDirectory(String outputDirectory) { + this.outputDirectory = outputDirectory; + return this; + } + + public String fhirVersion() { + return fhirVersion; + } + + public StripContentParams fhirVersion(String fhirVersion) { + this.fhirVersion = fhirVersion; + return this; + } + + public String cqlExportDirectory() { + return cqlExportDirectory; + } + + public StripContentParams cqlExportDirectory(String cqlExportDirectory) { + this.cqlExportDirectory = cqlExportDirectory; + return this; + } +} diff --git a/tooling/src/test/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperationTest.java b/tooling/src/test/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperationTest.java index 41a320824..8cb3f4079 100644 --- a/tooling/src/test/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperationTest.java +++ b/tooling/src/test/java/org/opencds/cqf/tooling/operation/StripGeneratedContentOperationTest.java @@ -1,6 +1,7 @@ package org.opencds.cqf.tooling.operation; import ca.uhn.fhir.context.FhirContext; + import org.hl7.fhir.r4.model.Library; import org.opencds.cqf.tooling.Operation; import org.testng.annotations.Test; @@ -13,23 +14,20 @@ import java.io.FileReader; import java.net.URISyntaxException; import java.nio.file.Path; -import java.nio.file.Paths; public class StripGeneratedContentOperationTest { - private static final String separator = System.getProperty("file.separator"); @Test public void test_strip_generated_content() throws URISyntaxException, FileNotFoundException { String dataInputPath = "strip-resources"; String operation = "StripGeneratedContent"; - String inputFilePath = StripGeneratedContentOperationTest.class.getResource(dataInputPath).toURI().getPath(); - String outputPath = "target/test-output/strip-generated-content"; + var inputFilePath = Path.of(StripGeneratedContentOperationTest.class.getResource(dataInputPath).toURI()); + var outputPath = Path.of("target", "test-output", "strip-generated-content"); String version = "r4"; - Library libraryBeforeStrip = (Library)FhirContext.forR4Cached().newJsonParser().parseResource( - new FileReader(inputFilePath+"/LibraryBreastCancerScreeningFHIR.json")); + new FileReader(inputFilePath + "/LibraryBreastCancerScreeningFHIR.json")); - assertEquals(libraryBeforeStrip.getContent().size(), 4); + assertEquals(libraryBeforeStrip.getContent().size(), 3); assertTrue(libraryBeforeStrip.hasText()); assertTrue(libraryBeforeStrip.hasParameter()); assertTrue(libraryBeforeStrip.hasDataRequirement()); @@ -39,30 +37,44 @@ public void test_strip_generated_content() throws URISyntaxException, FileNotFou Operation stripGeneratedContentOperation = new StripGeneratedContentOperation(); stripGeneratedContentOperation.execute(args); - Library libraryAfterStrip = null; - if (separator.equalsIgnoreCase("/")) { + File jsonFile = outputPath.resolve("LibraryBreastCancerScreeningFHIR.json").toFile(); - Path path = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath() + - "/../test-output/strip-generated-content"); - libraryAfterStrip = (Library)FhirContext.forR4Cached().newJsonParser().parseResource( - new FileReader(path + "/LibraryBreastCancerScreeningFHIR.json")); + var libraryAfterStrip = (Library) FhirContext.forR4Cached().newJsonParser().parseResource(new FileReader(jsonFile)); - }else{ + assertEquals(libraryAfterStrip.getContent().size(), 1); + // Cql should not be stripped or exported + assertTrue(libraryAfterStrip.getContent().get(0).hasData()); + assertFalse(libraryAfterStrip.hasText()); + assertFalse(libraryAfterStrip.hasParameter()); + assertFalse(libraryAfterStrip.hasDataRequirement()); + assertEquals(libraryAfterStrip.getRelatedArtifact().size(), 1); - File classLocation = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); - File parentDir = classLocation.getParentFile(); // Get the parent directory of the class location - File outputDir = new File(parentDir, "test-output/"); - File jsonFile = new File(outputDir, "strip-generated-contentLibraryBreastCancerScreeningFHIR.json"); + } - libraryAfterStrip = (Library) FhirContext.forR4Cached().newJsonParser().parseResource(new FileReader(jsonFile)); - } + @Test + void exportsCql() throws URISyntaxException, FileNotFoundException { + String dataInputPath = "strip-resources"; + String operation = "StripGeneratedContent"; + var inputFilePath = Path.of(StripGeneratedContentOperationTest.class.getResource(dataInputPath).toURI()); + var outputPath = Path.of("target", "test-output", "strip-generated-content-cql"); + + String[] args = { "-" + operation, "-ptr=" + inputFilePath, "-op=" + outputPath, "-cql=" + outputPath + File.separator + "cql"}; + Operation stripGeneratedContentOperation = new StripGeneratedContentOperation(); + stripGeneratedContentOperation.execute(args); + File jsonFile = outputPath.resolve("LibraryBreastCancerScreeningFHIR.json").toFile(); + var libraryAfterStrip = (Library) FhirContext.forR4Cached().newJsonParser().parseResource(new FileReader(jsonFile)); assertEquals(libraryAfterStrip.getContent().size(), 1); + // Cql should be exported + assertFalse(libraryAfterStrip.getContent().get(0).hasData()); + assertTrue(libraryAfterStrip.getContent().get(0).hasUrl()); assertFalse(libraryAfterStrip.hasText()); assertFalse(libraryAfterStrip.hasParameter()); assertFalse(libraryAfterStrip.hasDataRequirement()); assertEquals(libraryAfterStrip.getRelatedArtifact().size(), 1); + File cqlFile = outputPath.resolve("cql").resolve("BreastCancerScreeningFHIR.cql").toFile(); + assertTrue(cqlFile.exists()); } } diff --git a/tooling/src/test/resources/org/opencds/cqf/tooling/operation/strip-resources/LibraryBreastCancerScreeningFHIR.json b/tooling/src/test/resources/org/opencds/cqf/tooling/operation/strip-resources/LibraryBreastCancerScreeningFHIR.json index df9b50f34..f52e1f9d7 100644 --- a/tooling/src/test/resources/org/opencds/cqf/tooling/operation/strip-resources/LibraryBreastCancerScreeningFHIR.json +++ b/tooling/src/test/resources/org/opencds/cqf/tooling/operation/strip-resources/LibraryBreastCancerScreeningFHIR.json @@ -752,10 +752,6 @@ } ], "content": [ - { - "contentType": "text/cql", - "url": "someData" - }, { "contentType": "text/cql", "data": "bGlicmFyeSBCcmVhc3RDYW5jZXJTY3JlZW5pbmdGSElSIHZlcnNpb24gJzIuMC4wMDMnDQoNCnVzaW5nIEZISVIgdmVyc2lvbiAnNC4wLjEnDQoNCmluY2x1ZGUgRkhJUkhlbHBlcnMgdmVyc2lvbiAnNC4wLjAwMScgY2FsbGVkIEZISVJIZWxwZXJzDQppbmNsdWRlIFN1cHBsZW1lbnRhbERhdGFFbGVtZW50c0ZISVI0IHZlcnNpb24gJzIuMC4wMDAnIGNhbGxlZCBTREUNCmluY2x1ZGUgTUFUR2xvYmFsQ29tbW9uRnVuY3Rpb25zRkhJUjQgdmVyc2lvbiAnNi4wLjAwMCcgY2FsbGVkIEdsb2JhbA0KaW5jbHVkZSBBZHVsdE91dHBhdGllbnRFbmNvdW50ZXJzRkhJUjQgdmVyc2lvbiAnMi4wLjAwMCcgY2FsbGVkIEFkdWx0T3V0cGF0aWVudEVuY291bnRlcnMNCmluY2x1ZGUgSG9zcGljZUZISVI0IHZlcnNpb24gJzIuMC4wMDAnIGNhbGxlZCBIb3NwaWNlDQppbmNsdWRlIEFkdmFuY2VkSWxsbmVzc2FuZEZyYWlsdHlFeGNsdXNpb25FQ1FNRkhJUjQgdmVyc2lvbiAnNS4xMi4wMDAnIGNhbGxlZCBGcmFpbHR5DQoNCmNvZGVzeXN0ZW0gIkxPSU5DIjogJ2h0dHA6Ly9sb2luYy5vcmcnDQpjb2Rlc3lzdGVtICJTTk9NRURDVCI6ICdodHRwOi8vc25vbWVkLmluZm8vc2N0Jw0KDQp2YWx1ZXNldCAiQmlsYXRlcmFsIE1hc3RlY3RvbXkiOiAnaHR0cDovL2N0cy5ubG0ubmloLmdvdi9maGlyL1ZhbHVlU2V0LzIuMTYuODQwLjEuMTEzODgzLjMuNDY0LjEwMDMuMTk4LjEyLjEwMDUnDQp2YWx1ZXNldCAiRXRobmljaXR5IjogJ2h0dHA6Ly9jdHMubmxtLm5paC5nb3YvZmhpci9WYWx1ZVNldC8yLjE2Ljg0MC4xLjExNDIyMi40LjExLjgzNycNCnZhbHVlc2V0ICJGZW1hbGUiOiAnaHR0cDovL2N0cy5ubG0ubmloLmdvdi9maGlyL1ZhbHVlU2V0LzIuMTYuODQwLjEuMTEzODgzLjMuNTYwLjEwMC4yJw0KdmFsdWVzZXQgIkhpc3Rvcnkgb2YgYmlsYXRlcmFsIG1hc3RlY3RvbXkiOiAnaHR0cDovL2N0cy5ubG0ubmloLmdvdi9maGlyL1ZhbHVlU2V0LzIuMTYuODQwLjEuMTEzODgzLjMuNDY0LjEwMDMuMTk4LjEyLjEwNjgnDQp2YWx1ZXNldCAiTGVmdCI6ICdodHRwOi8vY3RzLm5sbS5uaWguZ292L2ZoaXIvVmFsdWVTZXQvMi4xNi44NDAuMS4xMTM4ODMuMy40NjQuMTAwMy4xMjIuMTIuMTAzNicNCnZhbHVlc2V0ICJNYW1tb2dyYXBoeSI6ICdodHRwOi8vY3RzLm5sbS5uaWguZ292L2ZoaXIvVmFsdWVTZXQvMi4xNi44NDAuMS4xMTM4ODMuMy40NjQuMTAwMy4xMDguMTIuMTAxOCcNCnZhbHVlc2V0ICJPTkMgQWRtaW5pc3RyYXRpdmUgU2V4IjogJ2h0dHA6Ly9jdHMubmxtLm5paC5nb3YvZmhpci9WYWx1ZVNldC8yLjE2Ljg0MC4xLjExMzc2Mi4xLjQuMScNCnZhbHVlc2V0ICJQYXllciI6ICdodHRwOi8vY3RzLm5sbS5uaWguZ292L2ZoaXIvVmFsdWVTZXQvMi4xNi44NDAuMS4xMTQyMjIuNC4xMS4zNTkxJw0KdmFsdWVzZXQgIlJhY2UiOiAnaHR0cDovL2N0cy5ubG0ubmloLmdvdi9maGlyL1ZhbHVlU2V0LzIuMTYuODQwLjEuMTE0MjIyLjQuMTEuODM2Jw0KdmFsdWVzZXQgIlJpZ2h0IjogJ2h0dHA6Ly9jdHMubmxtLm5paC5nb3YvZmhpci9WYWx1ZVNldC8yLjE2Ljg0MC4xLjExMzg4My4zLjQ2NC4xMDAzLjEyMi4xMi4xMDM1Jw0KdmFsdWVzZXQgIlN0YXR1cyBQb3N0IExlZnQgTWFzdGVjdG9teSI6ICdodHRwOi8vY3RzLm5sbS5uaWguZ292L2ZoaXIvVmFsdWVTZXQvMi4xNi44NDAuMS4xMTM4ODMuMy40NjQuMTAwMy4xOTguMTIuMTA2OScNCnZhbHVlc2V0ICJTdGF0dXMgUG9zdCBSaWdodCBNYXN0ZWN0b215IjogJ2h0dHA6Ly9jdHMubmxtLm5paC5nb3YvZmhpci9WYWx1ZVNldC8yLjE2Ljg0MC4xLjExMzg4My4zLjQ2NC4xMDAzLjE5OC4xMi4xMDcwJw0KdmFsdWVzZXQgIlVuaWxhdGVyYWwgTWFzdGVjdG9teSBMZWZ0IjogJ2h0dHA6Ly9jdHMubmxtLm5paC5nb3YvZmhpci9WYWx1ZVNldC8yLjE2Ljg0MC4xLjExMzg4My4zLjQ2NC4xMDAzLjE5OC4xMi4xMTMzJw0KdmFsdWVzZXQgIlVuaWxhdGVyYWwgTWFzdGVjdG9teSBSaWdodCI6ICdodHRwOi8vY3RzLm5sbS5uaWguZ292L2ZoaXIvVmFsdWVTZXQvMi4xNi44NDAuMS4xMTM4ODMuMy40NjQuMTAwMy4xOTguMTIuMTEzNCcNCnZhbHVlc2V0ICJVbmlsYXRlcmFsIE1hc3RlY3RvbXksIFVuc3BlY2lmaWVkIExhdGVyYWxpdHkiOiAnaHR0cDovL2N0cy5ubG0ubmloLmdvdi9maGlyL1ZhbHVlU2V0LzIuMTYuODQwLjEuMTEzODgzLjMuNDY0LjEwMDMuMTk4LjEyLjEwNzEnDQoNCmNvZGUgIkJpcnRoIGRhdGUiOiAnMjExMTItOCcgZnJvbSAiTE9JTkMiIGRpc3BsYXkgJ0JpcnRoIGRhdGUnDQpjb2RlICJMZWZ0IChxdWFsaWZpZXIgdmFsdWUpIjogJzc3NzEwMDAnIGZyb20gIlNOT01FRENUIiBkaXNwbGF5ICdMZWZ0IChxdWFsaWZpZXIgdmFsdWUpJw0KY29kZSAiUmlnaHQgKHF1YWxpZmllciB2YWx1ZSkiOiAnMjQwMjgwMDcnIGZyb20gIlNOT01FRENUIiBkaXNwbGF5ICdSaWdodCAocXVhbGlmaWVyIHZhbHVlKScNCg0KcGFyYW1ldGVyICJNZWFzdXJlbWVudCBQZXJpb2QiIEludGVydmFsPERhdGVUaW1lPg0KICBkZWZhdWx0IEludGVydmFsW0AyMDE5LTAxLTAxVDAwOjAwOjAwLjAsIEAyMDIwLTAxLTAxVDAwOjAwOjAwLjApDQoNCmNvbnRleHQgUGF0aWVudA0KDQpkZWZpbmUgIlNERSBFdGhuaWNpdHkiOg0KICBTREUuIlNERSBFdGhuaWNpdHkiDQoNCmRlZmluZSAiU0RFIFBheWVyIjoNCiAgU0RFLiJTREUgUGF5ZXIiDQoNCmRlZmluZSAiU0RFIFJhY2UiOg0KICBTREUuIlNERSBSYWNlIg0KDQpkZWZpbmUgIlNERSBTZXgiOg0KICBTREUuIlNERSBTZXgiDQoNCmRlZmluZSAiSW5pdGlhbCBQb3B1bGF0aW9uIjoNCiAgUGF0aWVudC5nZW5kZXIgPSAnZmVtYWxlJw0KICAJCQlhbmQgQWdlSW5ZZWFyc0F0KHN0YXJ0IG9mICJNZWFzdXJlbWVudCBQZXJpb2QiKSBiZXR3ZWVuIDUxIGFuZCA3NA0KICAJCQlhbmQgZXhpc3RzIEFkdWx0T3V0cGF0aWVudEVuY291bnRlcnMuIlF1YWxpZnlpbmcgRW5jb3VudGVycyINCg0KZGVmaW5lICJEZW5vbWluYXRvciI6DQogICJJbml0aWFsIFBvcHVsYXRpb24iDQoNCmRlZmluZSAiUmlnaHQgTWFzdGVjdG9teSBEaWFnbm9zaXMiOg0KICAoDQogIAkJCQkoIFtDb25kaXRpb246ICJTdGF0dXMgUG9zdCBSaWdodCBNYXN0ZWN0b215Il0NCiAgICAgICAgICAgIC8vd2hlcmUgQy5jbGluaWNhbFN0YXR1cyB+IFRvQ29uY2VwdChHbG9iYWwuImFjdGl2ZSIpIG5vdCBubmVlZGVkIGZvciBleGNsdXNpb24NCiAgICAgICAgICApDQogIAkJCQl1bmlvbiAoDQogIAkJCQkJCVtDb25kaXRpb246ICJVbmlsYXRlcmFsIE1hc3RlY3RvbXksIFVuc3BlY2lmaWVkIExhdGVyYWxpdHkiXSBVbmlsYXRlcmFsTWFzdGVjdG9teURpYWdub3Npcw0KICAJCQkJCQkJCXdoZXJlIFVuaWxhdGVyYWxNYXN0ZWN0b215RGlhZ25vc2lzLmJvZHlTaXRlIGluICJSaWdodCINCiAgICAgICAgICAgICAgICAvLyAgICBhbmQgVW5pbGF0ZXJhbE1hc3RlY3RvbXlEaWFnbm9zaXMuY2xpbmljYWxTdGF0dXMgfiBUb0NvbmNlcHQoR2xvYmFsLiJhY3RpdmUiKSBub3QgbmVlZGVkIGZvciBleGNsdXNpb24NCiAgCQkJCSkNCiAgCQkpIFJpZ2h0TWFzdGVjdG9teQ0KICAJCQkJd2hlcmUgR2xvYmFsLiJOb3JtYWxpemUgSW50ZXJ2YWwiKFJpZ2h0TWFzdGVjdG9teS5vbnNldCkgc3RhcnRzIG9uIG9yIGJlZm9yZSBlbmQgb2YgIk1lYXN1cmVtZW50IFBlcmlvZCINCg0KZGVmaW5lICJSaWdodCBNYXN0ZWN0b215IFByb2NlZHVyZSI6DQogIFtQcm9jZWR1cmU6ICJVbmlsYXRlcmFsIE1hc3RlY3RvbXkgUmlnaHQiXSBVbmlsYXRlcmFsTWFzdGVjdG9teVJpZ2h0UGVyZm9ybWVkDQogICAgICAgICAgIAkJd2hlcmUgR2xvYmFsLiJOb3JtYWxpemUgSW50ZXJ2YWwiKFVuaWxhdGVyYWxNYXN0ZWN0b215UmlnaHRQZXJmb3JtZWQucGVyZm9ybWVkKSBlbmRzIG9uIG9yIGJlZm9yZSBlbmQgb2YgIk1lYXN1cmVtZW50IFBlcmlvZCINCiAgICAgICAgICAgICAgICAgIGFuZCBVbmlsYXRlcmFsTWFzdGVjdG9teVJpZ2h0UGVyZm9ybWVkLnN0YXR1cyA9ICdjb21wbGV0ZWQnDQoNCmRlZmluZSAiTGVmdCBNYXN0ZWN0b215IFByb2NlZHVyZSI6DQogIFtQcm9jZWR1cmU6ICJVbmlsYXRlcmFsIE1hc3RlY3RvbXkgTGVmdCJdIFVuaWxhdGVyYWxNYXN0ZWN0b215TGVmdFBlcmZvcm1lZA0KICAgICAgICAgICAgICB3aGVyZSBHbG9iYWwuIk5vcm1hbGl6ZSBJbnRlcnZhbCIoVW5pbGF0ZXJhbE1hc3RlY3RvbXlMZWZ0UGVyZm9ybWVkLnBlcmZvcm1lZCkgZW5kcyBvbiBvciBiZWZvcmUgZW5kIG9mICJNZWFzdXJlbWVudCBQZXJpb2QiDQogICAgICAgICAgICAgICAgIGFuZCBVbmlsYXRlcmFsTWFzdGVjdG9teUxlZnRQZXJmb3JtZWQuc3RhdHVzID0gJ2NvbXBsZXRlZCcNCg0KZGVmaW5lICJMZWZ0IE1hc3RlY3RvbXkiOg0KICAoDQogIAkJICAgICggW0NvbmRpdGlvbjogIlN0YXR1cyBQb3N0IExlZnQgTWFzdGVjdG9teSJdDQogICAgICAgICAgLy8gIHdoZXJlIEMuY2xpbmljYWxTdGF0dXMgfiBUb0NvbmNlcHQoR2xvYmFsLiJhY3RpdmUiKSBub3QgbmVlZGVkIGZvciBleGNsdXNpb24NCiAgICAgICAgICApDQogIAkJCQl1bmlvbiAoDQogIAkJCQkJCVtDb25kaXRpb246ICJVbmlsYXRlcmFsIE1hc3RlY3RvbXksIFVuc3BlY2lmaWVkIExhdGVyYWxpdHkiXSBVbmlsYXRlcmFsTWFzdGVjdG9teURpYWdub3Npcw0KICAJCQkJCQkJCXdoZXJlIFVuaWxhdGVyYWxNYXN0ZWN0b215RGlhZ25vc2lzLmJvZHlTaXRlIGluICJMZWZ0Ig0KICAgICAgICAgICAgICAgICAgLy8gIGFuZCBVbmlsYXRlcmFsTWFzdGVjdG9teURpYWdub3Npcy5jbGluaWNhbFN0YXR1cyB+IFRvQ29uY2VwdChHbG9iYWwuImFjdGl2ZSIpIG5vdCBuZWVkZWQgZm9yIGV4Y2x1c2lvbg0KICAJCQkJKQ0KICAJCSkgTGVmdE1hc3RlY3RvbXkNCiAgCQkJCXdoZXJlIEdsb2JhbC4iTm9ybWFsaXplIEludGVydmFsIihMZWZ0TWFzdGVjdG9teS5vbnNldCkgc3RhcnRzIG9uIG9yIGJlZm9yZSBlbmQgb2YgIk1lYXN1cmVtZW50IFBlcmlvZCINCg0KZGVmaW5lICJCaWxhdGVyYWwgTWFzdGVjdG9teSBEaWFnbm9zaXMiOg0KICBbQ29uZGl0aW9uOiAiSGlzdG9yeSBvZiBiaWxhdGVyYWwgbWFzdGVjdG9teSJdIEJpbGF0ZXJhbE1hc3RlY3RvbXlIaXN0b3J5DQogIAkJCQl3aGVyZSBHbG9iYWwuIk5vcm1hbGl6ZSBJbnRlcnZhbCIoQmlsYXRlcmFsTWFzdGVjdG9teUhpc3Rvcnkub25zZXQpIHN0YXJ0cyBvbiBvciBiZWZvcmUgZW5kIG9mICJNZWFzdXJlbWVudCBQZXJpb2QiDQogICAgICAvLyAgICAgIGFuZCBCaWxhdGVyYWxNYXN0ZWN0b215SGlzdG9yeS5jbGluaWNhbFN0YXR1cyB+IFRvQ29uY2VwdChHbG9iYWwuImFjdGl2ZSIpIG5vdCBuZWVkZWQgYmVjYXVzZSBpdCBpcyBhbiBleGNsdXNpb24NCg0KZGVmaW5lICJCaWxhdGVyYWwgTWFzdGVjdG9teSBQcm9jZWR1cmUiOg0KICBbUHJvY2VkdXJlOiAiQmlsYXRlcmFsIE1hc3RlY3RvbXkiXSBCaWxhdGVyYWxNYXN0ZWN0b215UGVyZm9ybWVkDQogIAkJCQl3aGVyZSBHbG9iYWwuIk5vcm1hbGl6ZSBJbnRlcnZhbCIoQmlsYXRlcmFsTWFzdGVjdG9teVBlcmZvcm1lZC5wZXJmb3JtZWQpIGVuZHMgb24gb3IgYmVmb3JlIGVuZCBvZiAiTWVhc3VyZW1lbnQgUGVyaW9kIg0KICAJCQkJCQlhbmQgQmlsYXRlcmFsTWFzdGVjdG9teVBlcmZvcm1lZC5zdGF0dXMgPSAnY29tcGxldGVkJw0KDQpkZWZpbmUgIk51bWVyYXRvciI6DQogIGV4aXN0cyAoDQogIAkJCQlbRGlhZ25vc3RpY1JlcG9ydDogIk1hbW1vZ3JhcGh5Il0gTWFtbW9ncmFtDQogIAkJCQkJCXdoZXJlICggR2xvYmFsLiJOb3JtYWxpemUgSW50ZXJ2YWwiKE1hbW1vZ3JhbS5lZmZlY3RpdmUpIGVuZHMgMjcgbW9udGhzIG9yIGxlc3Mgb24gb3IgYmVmb3JlIGVuZCBvZiAiTWVhc3VyZW1lbnQgUGVyaW9kIiApDQogIAkJCQkJCQkJYW5kIE1hbW1vZ3JhbS5zdGF0dXMgaW4geyAnZmluYWwnLCAnYW1lbmRlZCcsICdjb3JyZWN0ZWQnLCAnYXBwZW5kZWQnIH0NCiAgCQkpDQoNCmRlZmluZSAiRGVub21pbmF0b3IgRXhjbHVzaW9uIjoNCiAgSG9zcGljZS4iSGFzIEhvc3BpY2UiDQogIAkJCQlvciAoKCBleGlzdHMgIlJpZ2h0IE1hc3RlY3RvbXkgRGlhZ25vc2lzIg0KICAJCQkJb3IgZXhpc3RzICJSaWdodCBNYXN0ZWN0b215IFByb2NlZHVyZSIpDQogICAgICAgICAgICBhbmQgKGV4aXN0cyAiTGVmdCBNYXN0ZWN0b215IiBvciBleGlzdHMgIkxlZnQgTWFzdGVjdG9teSBQcm9jZWR1cmUiKSkNCiAgCQkJCW9yIGV4aXN0cyAiQmlsYXRlcmFsIE1hc3RlY3RvbXkgRGlhZ25vc2lzIg0KICAJCQkJb3IgZXhpc3RzICJCaWxhdGVyYWwgTWFzdGVjdG9teSBQcm9jZWR1cmUiDQogICAgICAgICAgb3IgRnJhaWx0eS4iQWR2YW5jZWQgSWxsbmVzcyBhbmQgRnJhaWx0eSBFeGNsdXNpb24gTm90IEluY2x1ZGluZyBPdmVyIEFnZSA4MCINCiAgICAgICAgICBvciAoQWdlSW5ZZWFyc0F0KHN0YXJ0IG9mICJNZWFzdXJlbWVudCBQZXJpb2QiKSA+PSA2NQ0KICAgICAgICAgICAgYW5kIEZyYWlsdHkuIkxvbmcgVGVybSBDYXJlIFBlcmlvZHMgTG9uZ2VyIFRoYW4gOTAgQ29uc2VjdXRpdmUgRGF5cyIpDQo="