Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

BUG FIX (Issue 537): ExtractMatBundle Non-IG Folder Structure Handling #539

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.opencds.cqf.tooling.Operation;
import org.opencds.cqf.tooling.common.ThreadUtils;
import org.opencds.cqf.tooling.utilities.BundleUtils;
import org.opencds.cqf.tooling.utilities.LogUtils;
import org.opencds.cqf.tooling.utilities.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -19,9 +18,9 @@
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

public class ExtractMatBundleOperation extends Operation {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
Expand Down Expand Up @@ -60,7 +59,9 @@ public void execute(String[] args) {
} else if (i == 0 && !args[i].equalsIgnoreCase("-ExtractMatBundle")) {
throw new IllegalArgumentException("Insufficient argument structure. " +
"Usage Example: mvn exec:java -Dexec.args=\"-ExtractMatBundle " +
"/Development/ecqm-content-r4/bundles/mat/EXM124/EXM124.json -v=r4");
File.separator + "Development" + File.separator + "ecqm-content-r4" +
File.separator + "bundles" + File.separator + "mat" +
File.separator + "EXM124" + File.separator + "EXM124.json -v=r4");
}

//position 1 is the location of file or directory. Determine which:
Expand Down Expand Up @@ -135,7 +136,7 @@ public void execute(String[] args) {
if (filesInDir != null && filesInDir.length > 0) {
//use recursive calls to build up task list:
ThreadUtils.executeTasks(processFilesInDir(filesInDir, version, suppressNarrative));
}else{
} else {
logger.info(ERROR_DIR_IS_EMPTY);
return;
}
Expand All @@ -146,7 +147,7 @@ public void execute(String[] args) {

if (!processedBundleCollection.isEmpty()) {
logger.info("Successfully extracted " + processedBundleCollection.size() + " resource(s): \n" + String.join("\n", processedBundleCollection));
}else{
} else {
logger.info("ExtractMatBundleOperation ended with no resources extracted!");
}
}
Expand Down Expand Up @@ -268,6 +269,22 @@ void processSingleFile(File bundleFile, String version, boolean suppressNarrativ
}


private Path getParentBundleDir(String directory){
Path parent = Paths.get(directory);
// Traverse to the parent of 'bundles'
while (parent != null &&
parent.getFileName() != null &&
!parent.getFileName().toString().equalsIgnoreCase("bundles")) {
parent = parent.getParent();
}

// Move one level up to get the directory before 'bundles'
if (parent != null) {
return parent.getParent();
} else {
return null;
}
}

/**
* Iterates through the files and properly renames and moves them to the proper place
Expand Down Expand Up @@ -298,14 +315,21 @@ private void moveAndRenameFiles(String outputDir, FhirContext context, String ve
// The extractor code names them using the resource type and ID
// We want to name them without the resource type, use name, and if needed version
String resourceName;
Path newOutputDirectory = Paths.get(outputDir.substring(0, outputDir.indexOf("bundles")), "input");
Path newLibraryDirectory = Paths.get(newOutputDirectory.toString(), "resources/library");
newLibraryDirectory.toFile().mkdirs();

// https://github.com/cqframework/cqf-tooling/issues/537
// if there's no bundle directory (getParentBundleDir returned null) we'll use resourceOutputDir (original outputDir):
Path resourceOutputDir = Paths.get(outputDir).toAbsolutePath();

Path newOutputDirectory = Paths.get(
Objects.requireNonNullElse(getParentBundleDir(outputDir)
, resourceOutputDir)
+ File.separator + "input");

Path newLibraryDirectory = Paths.get(newOutputDirectory.toString(), "resources" + File.separator + "library");
Path newCqlDirectory = Paths.get(newOutputDirectory.toString(), "cql");
newCqlDirectory.toFile().mkdirs();
Path newMeasureDirectory = Paths.get(newOutputDirectory.toString(), "resources/measure");
newMeasureDirectory.toFile().mkdirs();
if (version == "stu3) ") {
Path newMeasureDirectory = Paths.get(newOutputDirectory.toString(), "resources" + File.separator + "measure");

if (version.equals(VERSION_STU3)) {
if (theResource instanceof org.hl7.fhir.dstu3.model.Library) {
org.hl7.fhir.dstu3.model.Library theLibrary = (org.hl7.fhir.dstu3.model.Library) theResource;
resourceName = theLibrary.getName();
Expand All @@ -329,7 +353,7 @@ private void moveAndRenameFiles(String outputDir, FhirContext context, String ve
// Forcing the encoding to JSON here to make everything the same in input directory
ResourceUtils.outputResourceByName(theResource, "json", context, newMeasureDirectory.toString(), resourceName);
}
} else if (version == VERSION_R4) {
} else if (version.equals(VERSION_R4)) {
if (theResource instanceof org.hl7.fhir.r4.model.Library) {
org.hl7.fhir.r4.model.Library theLibrary = (org.hl7.fhir.r4.model.Library) theResource;
resourceName = theLibrary.getName();
Expand Down Expand Up @@ -372,7 +396,13 @@ private void extractStu3CQL(org.hl7.fhir.dstu3.model.Library theLibrary, String
String encodedString = Base64.getEncoder().encodeToString(encodedBytes);
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
try {
FileUtils.writeByteArrayToFile(new File(cqlFilename), decodedBytes);
File outputFile = new File(cqlFilename);
// Ensure the parent directory exists
File parentDir = outputFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
FileUtils.writeByteArrayToFile(outputFile, decodedBytes);
} catch (IOException e) {
throw new RuntimeException(cqlFilename + ": " + e.getMessage());
}
Expand All @@ -395,7 +425,13 @@ private void extractR4CQL(org.hl7.fhir.r4.model.Library theLibrary, String cqlFi
String encodedString = Base64.getEncoder().encodeToString(encodedBytes);
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
try {
FileUtils.writeByteArrayToFile(new File(cqlFilename), decodedBytes);
File outputFile = new File(cqlFilename);
// Ensure the parent directory exists
File parentDir = outputFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
FileUtils.writeByteArrayToFile(outputFile, decodedBytes);
} catch (IOException e) {
throw new RuntimeException(cqlFilename + ": " + e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,13 +838,20 @@ public static void outputResource(IBaseResource resource, String encoding, FhirC
}

public static void outputResourceByName(IBaseResource resource, String encoding, FhirContext context, String outputPath, String name) {
try (FileOutputStream writer = new FileOutputStream(outputPath + "/" + name + "." + encoding)) {
writer.write(
encoding.equals("json")
? context.newJsonParser().setPrettyPrint(true).encodeResourceToString(resource).getBytes()
: context.newXmlParser().setPrettyPrint(true).encodeResourceToString(resource).getBytes()
);
writer.flush();
try {
File directory = new File(outputPath);
if (!directory.exists()) {
directory.mkdirs(); // Ensure the directory exists
}

try (FileOutputStream writer = new FileOutputStream(outputPath + "/" + name + "." + encoding)) {
writer.write(
encoding.equals("json")
? context.newJsonParser().setPrettyPrint(true).encodeResourceToString(resource).getBytes()
: context.newXmlParser().setPrettyPrint(true).encodeResourceToString(resource).getBytes()
);
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
package org.opencds.cqf.tooling.operation;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.fail;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.testng.Assert.*;

public class ExtractMatBundleOperationTest {

private ExtractMatBundleOperation operation;

@BeforeClass
@BeforeMethod
public void setUp() {
operation = new ExtractMatBundleOperation();
}
Expand Down Expand Up @@ -211,9 +208,61 @@ public void run() {
}
File[] files = emptyDir.listFiles();
assertNotNull(files);
assertEquals(16, files.length);
assertEquals(17, files.length);
}

/**
* In response to issue https://github.com/cqframework/cqf-tooling/issues/537
* The ExtractMATBundle process defaults to the location of the bundle file
* when no IG folder structure exists (no bundle folder found.)
*
* @throws IOException
*/
@Test
public void TestExtractMatBundleWithNonIGStructuredDirectory() throws IOException {
ClassLoader classLoader = getClass().getClassLoader();
String resourcePath = "org/opencds/cqf/tooling/operation/ExtractMatBundle/bundles_small/";
URL resourceUrl = classLoader.getResource(resourcePath);
if (resourceUrl == null) {
throw new IllegalArgumentException("Resource not found: " + resourcePath);
}

// Create a temporary directory named "noIG" and does not include the name bundles
File tempDir = Files.createTempDirectory("noIG").toFile();
tempDir.deleteOnExit();

// Copy files from resourcePath to the temporary directory
File sourceDir = new File(resourceUrl.getFile());
if (!sourceDir.isDirectory()) {
throw new IllegalArgumentException("Resource path is not a directory: " + resourcePath);
}

//Copy our test files to the temp folder so the source location of the bundle is the temp folder
for (File file : sourceDir.listFiles()) {
File destFile = new File(tempDir, file.getName());
Files.copy(file.toPath(), destFile.toPath());
}

Thread executionThread = new Thread(() ->
operation.execute(new String[]{"-ExtractMATBundle", tempDir.getAbsolutePath(), "-dir"})
);

executionThread.start();
try {
executionThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

// Validate results in the temporary directory
File[] files = tempDir.listFiles();
assertNotNull(files);

//Directory should now include 1 input folder, original json bundles (6), and extracted files (16)
assertEquals(23, files.length);
}


@Test
public void TestExtractMatBundleWithDirectoryAndSubDirectories() throws IOException {
ClassLoader classLoader = getClass().getClassLoader();
Expand All @@ -240,7 +289,7 @@ public void run() {
}
File[] files = emptyDir.listFiles();
assertNotNull(files);
assertEquals(41, files.length);
assertEquals(42, files.length);
}

@Test
Expand All @@ -265,6 +314,6 @@ public void run() {

File[] files = emptyDir.listFiles();
assertNotNull(files);
assertEquals(files.length, 16);
assertEquals(files.length, 17);
}
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading
Loading