Skip to content

Commit c65d3c5

Browse files
committed
Bringing back BundleToTransaction operation
1 parent 37a4a89 commit c65d3c5

3 files changed

Lines changed: 244 additions & 0 deletions

File tree

tooling-cli/src/main/java/org/opencds/cqf/tooling/cli/command/BundleCommand.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
BundleCommand.Resources.class,
1515
BundleCommand.ToResources.class,
1616
BundleCommand.ToTransaction.class,
17+
BundleCommand.Transaction.class,
1718
BundleCommand.Post.class,
1819
BundleCommand.Publish.class,
1920
})
@@ -60,6 +61,19 @@ protected String getOperationName() {
6061
return "MakeTransaction";
6162
}
6263

64+
@Override
65+
protected Operation createOperation() {
66+
return new ExecutableOperationAdapter(new org.opencds.cqf.tooling.operations.bundle.BundleToTransaction());
67+
}
68+
}
69+
70+
@Command(name = "transaction", description = "Execute Bundles as transactions on a FHIR server")
71+
static class Transaction extends OperationCommand {
72+
@Override
73+
protected String getOperationName() {
74+
return "BundleTransaction";
75+
}
76+
6377
@Override
6478
protected Operation createOperation() {
6579
return new ExecutableOperationAdapter(new org.opencds.cqf.tooling.operations.bundle.BundleTransaction());
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package org.opencds.cqf.tooling.operations.bundle;
2+
3+
import ca.uhn.fhir.context.FhirContext;
4+
import ca.uhn.fhir.context.FhirVersionEnum;
5+
import java.io.File;
6+
import java.io.FileNotFoundException;
7+
import java.io.FileOutputStream;
8+
import java.io.FileReader;
9+
import java.io.IOException;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
import org.hl7.fhir.instance.model.api.IBaseResource;
13+
import org.opencds.cqf.tooling.operations.ExecutableOperation;
14+
import org.opencds.cqf.tooling.operations.Operation;
15+
import org.opencds.cqf.tooling.operations.OperationParam;
16+
import org.opencds.cqf.tooling.utilities.FhirContextCache;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
20+
@Operation(name = "MakeTransaction")
21+
public class BundleToTransaction implements ExecutableOperation {
22+
private static final Logger logger = LoggerFactory.getLogger(BundleToTransaction.class);
23+
24+
private static final String DEFAULT_OUTPUT_PATH =
25+
"src/main/resources/org/opencds/cqf/tooling/bundle/output";
26+
27+
@OperationParam(
28+
alias = {"e", "encoding"},
29+
setter = "setEncoding",
30+
defaultValue = "json",
31+
description = "Output encoding { json, xml } (default json)")
32+
private String encoding;
33+
34+
@OperationParam(
35+
alias = {"p", "path"},
36+
setter = "setPath",
37+
required = true,
38+
description = "Path to a Bundle file or directory of Bundles (required)")
39+
private String path;
40+
41+
@OperationParam(
42+
alias = {"op", "outputpath"},
43+
setter = "setOutputPath",
44+
description = "Output directory for the converted transaction Bundles")
45+
private String outputPath;
46+
47+
@OperationParam(
48+
alias = {"v", "version"},
49+
setter = "setVersion",
50+
defaultValue = "r4",
51+
description = "FHIR version { dstu2, stu3, r4 } (default r4)")
52+
private String version;
53+
54+
@Override
55+
public void execute() {
56+
if (path == null) {
57+
throw new IllegalArgumentException("The path to a Bundle or directory of resources is required");
58+
}
59+
60+
if (outputPath == null) {
61+
outputPath = DEFAULT_OUTPUT_PATH;
62+
}
63+
64+
File outputDir = new File(outputPath);
65+
if (!outputDir.exists()) {
66+
outputDir.mkdirs();
67+
}
68+
69+
if (encoding == null) {
70+
encoding = "json";
71+
}
72+
73+
FhirContext context = FhirContextCache.getContext(version);
74+
75+
File file = new File(path);
76+
File[] bundles;
77+
if (file.isDirectory()) {
78+
bundles = file.listFiles();
79+
} else {
80+
bundles = new File[] {file};
81+
}
82+
83+
List<IBaseResource> resources = getResources(bundles, context);
84+
convertToTransaction(resources, context);
85+
}
86+
87+
private void convertToTransaction(List<IBaseResource> resources, FhirContext context) {
88+
if (context.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
89+
for (IBaseResource resource : resources) {
90+
if (resource instanceof org.hl7.fhir.dstu3.model.Bundle) {
91+
org.hl7.fhir.dstu3.model.Bundle bundle = (org.hl7.fhir.dstu3.model.Bundle) resource;
92+
for (org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent entry : bundle.getEntry()) {
93+
if (entry.getResource() != null) {
94+
entry.setRequest(new org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent()
95+
.setUrl(entry.getResource().getId())
96+
.setMethod(org.hl7.fhir.dstu3.model.Bundle.HTTPVerb.PUT));
97+
}
98+
}
99+
bundle.setType(org.hl7.fhir.dstu3.model.Bundle.BundleType.TRANSACTION);
100+
writeResource(bundle, context);
101+
}
102+
}
103+
} else if (context.getVersion().getVersion() == FhirVersionEnum.R4) {
104+
for (IBaseResource resource : resources) {
105+
if (resource instanceof org.hl7.fhir.r4.model.Bundle) {
106+
org.hl7.fhir.r4.model.Bundle bundle = (org.hl7.fhir.r4.model.Bundle) resource;
107+
for (org.hl7.fhir.r4.model.Bundle.BundleEntryComponent entry : bundle.getEntry()) {
108+
if (entry.getResource() != null) {
109+
entry.setRequest(new org.hl7.fhir.r4.model.Bundle.BundleEntryRequestComponent()
110+
.setUrl(entry.getResource().getId())
111+
.setMethod(org.hl7.fhir.r4.model.Bundle.HTTPVerb.PUT));
112+
}
113+
}
114+
bundle.setType(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION);
115+
writeResource(bundle, context);
116+
}
117+
}
118+
}
119+
}
120+
121+
private List<IBaseResource> getResources(File[] files, FhirContext context) {
122+
List<IBaseResource> resources = new ArrayList<>();
123+
for (File file : files) {
124+
if (file.isDirectory()) {
125+
resources.addAll(getResources(file.listFiles(), context));
126+
continue;
127+
}
128+
129+
IBaseResource resource = null;
130+
if (file.getPath().endsWith(".xml")) {
131+
try {
132+
resource = context.newXmlParser().parseResource(new FileReader(file));
133+
} catch (FileNotFoundException e) {
134+
throw new RuntimeException(e.getMessage(), e);
135+
} catch (Exception e) {
136+
logger.debug("Skipping non-parseable file: {}", file.getName());
137+
continue;
138+
}
139+
} else if (file.getPath().endsWith(".json")) {
140+
try {
141+
resource = context.newJsonParser().parseResource(new FileReader(file));
142+
} catch (FileNotFoundException e) {
143+
throw new RuntimeException(e.getMessage(), e);
144+
} catch (Exception e) {
145+
logger.debug("Skipping non-parseable file: {}", file.getName());
146+
continue;
147+
}
148+
} else {
149+
continue;
150+
}
151+
resources.add(resource);
152+
}
153+
return resources;
154+
}
155+
156+
private void writeResource(IBaseResource resource, FhirContext context) {
157+
String fileName = resource.getIdElement().getResourceType() + "-"
158+
+ resource.getIdElement().getIdPart() + "." + encoding;
159+
File outputFile = new File(outputPath, fileName);
160+
try (FileOutputStream writer = new FileOutputStream(outputFile)) {
161+
String encoded = encoding.equals("json")
162+
? context.newJsonParser().setPrettyPrint(true).encodeResourceToString(resource)
163+
: context.newXmlParser().setPrettyPrint(true).encodeResourceToString(resource);
164+
writer.write(encoded.getBytes());
165+
writer.flush();
166+
} catch (IOException e) {
167+
throw new RuntimeException(e.getMessage(), e);
168+
}
169+
}
170+
171+
public void setEncoding(String encoding) {
172+
this.encoding = encoding;
173+
}
174+
175+
public void setPath(String path) {
176+
this.path = path;
177+
}
178+
179+
public void setOutputPath(String outputPath) {
180+
this.outputPath = outputPath;
181+
}
182+
183+
public void setVersion(String version) {
184+
this.version = version;
185+
}
186+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.opencds.cqf.tooling.operation;
2+
3+
import java.io.File;
4+
import org.opencds.cqf.tooling.Operation;
5+
import org.opencds.cqf.tooling.operations.ExecutableOperationAdapter;
6+
import org.opencds.cqf.tooling.operations.bundle.BundleToTransaction;
7+
import org.testng.Assert;
8+
import org.testng.annotations.Test;
9+
10+
public class BundleToTransactionTest {
11+
12+
@Test
13+
public void testExecute_BundleDecomposition() {
14+
String projectPath = System.getProperty("user.dir");
15+
String relativeJsonPath = "src/main/resources/libraryevaluationtest-bundle.json";
16+
String jsonFilePath = projectPath + File.separator + relativeJsonPath;
17+
String relativePath = "target/test-output/bundleToTransactionResults";
18+
19+
Operation operation = new ExecutableOperationAdapter(new BundleToTransaction());
20+
String[] args = new String[] {
21+
"-MakeTransaction",
22+
"-p=" + jsonFilePath,
23+
"-e=json",
24+
"-op=" + projectPath + File.separator + relativePath
25+
};
26+
operation.execute(args);
27+
28+
File resultDir = new File(projectPath + File.separator + relativePath);
29+
Assert.assertTrue(resultDir.exists() && resultDir.isDirectory(), "Result directory does not exist.");
30+
31+
String expectedFile = "Bundle-libraryevaluationtest-bundle.json";
32+
File[] actualFiles = resultDir.listFiles((dir, name) -> name.endsWith(".json"));
33+
Assert.assertNotNull(actualFiles, "Bundle resource folder should not be null.");
34+
35+
boolean found = false;
36+
for (File file : actualFiles) {
37+
if (file.getName().equals(expectedFile)) {
38+
found = true;
39+
break;
40+
}
41+
}
42+
Assert.assertTrue(found, "Expected file not found: " + expectedFile);
43+
}
44+
}

0 commit comments

Comments
 (0)