Skip to content

Commit 32b5d4b

Browse files
c-schulerbrynrhodesJPercivalbkaney
authored
Package fix (#557)
* Fixes bundling to not have to recompile CQL, just uses related artifacts of the refreshed content * Updated PlanDefinition and Measure Packaging * Test fix * Fix bug where only direct deps should be in the refreshed library (#558) fix bug where only direct deps should be in the refreshed library * Applying feedback --------- Co-authored-by: Bryn Rhodes <bryn@databaseconsultinggroup.com> Co-authored-by: Bryn Rhodes <brynrhodes@users.noreply.github.com> Co-authored-by: JP <jonathan.i.percival@gmail.com> Co-authored-by: Brian Kaney <brian@vermonster.com>
1 parent 0bead88 commit 32b5d4b

File tree

16 files changed

+969
-109
lines changed

16 files changed

+969
-109
lines changed

tooling/src/main/java/org/opencds/cqf/tooling/library/LibraryProcessor.java

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import org.opencds.cqf.tooling.processor.BaseProcessor;
1919
import org.opencds.cqf.tooling.processor.CqlProcessor;
2020
import org.opencds.cqf.tooling.processor.IGProcessor;
21-
import org.opencds.cqf.tooling.utilities.IOUtils;
2221
import org.opencds.cqf.tooling.utilities.IOUtils.Encoding;
2322
import org.opencds.cqf.tooling.utilities.ResourceUtils;
2423
import org.slf4j.Logger;
@@ -103,52 +102,47 @@ public List<String> refreshIgLibraryContent(BaseProcessor parentContext, Encodin
103102
* Bundles library dependencies for a given FHIR library file and populates the provided resource map.
104103
* This method executes asynchronously by invoking the associated task queue.
105104
*
106-
* @param path The path to the FHIR library file.
105+
* @param library The Library resource.
107106
* @param fhirContext The FHIR context to use for processing resources.
108107
* @param resources The map to populate with library resources.
109108
* @param encoding The encoding to use for reading and processing resources.
110109
* @param versioned A boolean indicating whether to consider versioned resources.
111110
*/
112-
public void bundleLibraryDependencies(String path, FhirContext fhirContext, Map<String, IBaseResource> resources,
111+
public void bundleLibraryDependencies(IBaseResource library, FhirContext fhirContext, Map<String, IBaseResource> resources,
113112
Encoding encoding, boolean versioned) throws Exception {
114-
Queue<Callable<Void>> bundleLibraryDependenciesTasks = bundleLibraryDependenciesTasks(path, fhirContext, resources, encoding, versioned);
113+
Queue<Callable<Void>> bundleLibraryDependenciesTasks = bundleLibraryDependenciesTasks(library, fhirContext, resources, encoding, versioned);
115114
ThreadUtils.executeTasks(bundleLibraryDependenciesTasks);
116115
}
117116

118117
/**
119118
* Recursively bundles library dependencies for a given FHIR library file and populates the provided resource map.
120119
* Each dependency is added as a Callable task to be executed asynchronously.
121120
*
122-
* @param path The path to the FHIR library file.
121+
* @param library The Library resource
123122
* @param fhirContext The FHIR context to use for processing resources.
124123
* @param resources The map to populate with library resources.
125124
* @param encoding The encoding to use for reading and processing resources.
126125
* @param versioned A boolean indicating whether to consider versioned resources.
127126
* @return A queue of Callable tasks, each representing the bundling of a library dependency.
128127
* The Callable returns null (Void) and is meant for asynchronous execution.
129128
*/
130-
public Queue<Callable<Void>> bundleLibraryDependenciesTasks(String path, FhirContext fhirContext, Map<String, IBaseResource> resources,
129+
public Queue<Callable<Void>> bundleLibraryDependenciesTasks(IBaseResource library, FhirContext fhirContext, Map<String, IBaseResource> resources,
131130
Encoding encoding, boolean versioned) throws Exception {
132131

133132
Queue<Callable<Void>> returnTasks = new ConcurrentLinkedQueue<>();
134133

135-
String fileName = FilenameUtils.getName(path);
136-
boolean prefixed = fileName.toLowerCase().startsWith("library-");
137-
Map<String, IBaseResource> dependencies = ResourceUtils.getDepLibraryResources(path, fhirContext, encoding, versioned, logger);
138-
// String currentResourceID = IOUtils.getTypeQualifiedResourceId(path, fhirContext);
139-
for (IBaseResource resource : dependencies.values()) {
140-
returnTasks.add(() -> {
141-
resources.putIfAbsent(resource.getIdElement().getIdPart(), resource);
142-
143-
// NOTE: Assuming dependency library will be in directory of dependent.
144-
String dependencyPath = IOUtils.getResourceFileName(IOUtils.getResourceDirectory(path), resource, encoding, fhirContext, versioned, prefixed);
134+
returnTasks.add(() -> {
135+
Set<String> missingDependencies = new HashSet<>();
136+
Map<String, IBaseResource> dependencies = ResourceUtils.getDepLibraryResources(library, fhirContext, true, versioned, missingDependencies);
137+
for (IBaseResource resource : dependencies.values()) {
138+
resources.putIfAbsent(resource.fhirType() + '/' + resource.getIdElement().getIdPart(), resource);
139+
}
145140

146-
returnTasks.addAll(bundleLibraryDependenciesTasks(dependencyPath, fhirContext, resources, encoding, versioned));
141+
// TODO: Return missing dependencies as translator warnings...
147142

148-
//return statement needed for Callable<Void>
149-
return null;
150-
});
151-
}
143+
//return statement needed for Callable<Void>
144+
return null;
145+
});
152146
return returnTasks;
153147
}
154148

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package org.opencds.cqf.tooling.packaging;
2+
3+
import ca.uhn.fhir.context.FhirContext;
4+
import ca.uhn.fhir.rest.client.api.IGenericClient;
5+
import org.apache.commons.io.FilenameUtils;
6+
import org.hl7.fhir.instance.model.api.IBaseResource;
7+
import org.opencds.cqf.tooling.utilities.IOUtils;
8+
import org.opencds.cqf.tooling.utilities.ResourceUtils;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
import java.util.HashSet;
13+
import java.util.LinkedHashSet;
14+
import java.util.Set;
15+
16+
public abstract class Package<T extends IBaseResource> {
17+
18+
private static final Logger logger = LoggerFactory.getLogger(Package.class);
19+
20+
/*
21+
22+
CRMI Packaging Guidance
23+
24+
In general, artifacts such as libraries, measures, and test cases can be packaged as a Bundle of type transaction.
25+
However, since large artifact packages may span multiple bundles, the type collection MAY be used as well. In that
26+
case, the bundles SHOULD be processed as a unit (i.e. as a single transaction). The intent of splitting the bundles
27+
is to allow large packages to be processed, but in the case that they are split, transactional considerations are
28+
the responsibility of the consumer.
29+
30+
An artifact bundle contains the artifact as the first entry in the bundle, and optionally the dependencies and
31+
associated artifacts as subsequent entries as follows:
32+
1. Artifact: The main artifact resource for the package
33+
2. Dependencies: Any dependent artifact referenced by the main artifact
34+
3. Test Cases: Any test cases defined for the artifact
35+
36+
*/
37+
38+
private T mainArtifact;
39+
private IBaseResource primaryLibrary;
40+
private Set<IBaseResource> dependencies;
41+
private TestPackage<?,?> testPackage;
42+
private String igRoot;
43+
private FhirContext fhirContext;
44+
private boolean includeDependencies;
45+
private boolean includeTerminology;
46+
private boolean includeTests;
47+
private String fhirServerUrl;
48+
private IGenericClient fhirClient;
49+
private String bundleOutputPath;
50+
51+
public Package(String igRoot, FhirContext fhirContext, boolean includeDependencies, boolean includeTerminology, boolean includeTests, String fhirServerUrl) {
52+
this.igRoot = igRoot;
53+
this.fhirContext = fhirContext;
54+
this.includeDependencies = includeDependencies;
55+
this.includeTerminology = includeTerminology;
56+
this.includeTests = includeTests;
57+
this.fhirServerUrl = fhirServerUrl;
58+
if (this.fhirServerUrl != null) {
59+
this.fhirClient = this.fhirContext.newRestfulGenericClient(fhirServerUrl);
60+
}
61+
this.bundleOutputPath = FilenameUtils.concat(igRoot, "bundles");
62+
}
63+
64+
public abstract T resolveMainArtifact();
65+
public abstract Set<IBaseResource> resolveDependencies(T mainArtifact);
66+
public abstract TestPackage<?, ?> resolveTests(T mainArtifact);
67+
public abstract void output();
68+
69+
public void packageArtifact() {
70+
this.mainArtifact = resolveMainArtifact();
71+
this.dependencies = resolveDependencies(this.mainArtifact);
72+
if (includeTests) {
73+
this.testPackage = resolveTests(this.mainArtifact);
74+
}
75+
output();
76+
}
77+
78+
public void resolvePrimaryLibraryDependencies(IBaseResource mainArtifact, FhirContext fhirContext, LinkedHashSet<IBaseResource> dependencies) {
79+
var primaryLibrary = IOUtils.getLibraryUrlMap(fhirContext).get(
80+
ResourceUtils.getPrimaryLibraryUrl(mainArtifact, fhirContext));
81+
if (getPrimaryLibrary() == null) { // we want to save the primary library for the artifact being bundled not dependency libraries
82+
setPrimaryLibrary(primaryLibrary);
83+
}
84+
85+
var missingDependencies = new HashSet<String>();
86+
if (includeDependencies) {
87+
dependencies.add(primaryLibrary);
88+
var dependencyLibraries = ResourceUtils.getDepLibraryResources(
89+
primaryLibrary, fhirContext, true, false, missingDependencies);
90+
dependencies.addAll(dependencyLibraries.values());
91+
}
92+
93+
if (includeTerminology) {
94+
var dependencyValueSets = ResourceUtils.getDepValueSetResources(
95+
primaryLibrary, fhirContext, true, missingDependencies);
96+
dependencies.addAll(dependencyValueSets.values());
97+
}
98+
99+
missingDependencies.forEach(missing -> logger.warn("Unable to package dependency: {}", missing));
100+
}
101+
102+
public T getMainArtifact() {
103+
return mainArtifact;
104+
}
105+
106+
public void setMainArtifact(T mainArtifact) {
107+
this.mainArtifact = mainArtifact;
108+
}
109+
110+
public IBaseResource getPrimaryLibrary() {
111+
return primaryLibrary;
112+
}
113+
114+
public void setPrimaryLibrary(IBaseResource primaryLibrary) {
115+
this.primaryLibrary = primaryLibrary;
116+
}
117+
118+
public Set<IBaseResource> getDependencies() {
119+
return dependencies;
120+
}
121+
122+
public void setDependencies(Set<IBaseResource> dependencies) {
123+
this.dependencies = dependencies;
124+
}
125+
126+
public TestPackage<?,?> getTestPackage() {
127+
return testPackage;
128+
}
129+
130+
public void setTests(TestPackage<?,?> testPackage) {
131+
this.testPackage = testPackage;
132+
}
133+
134+
public String getIgRoot() {
135+
return igRoot;
136+
}
137+
138+
public void setIgRoot(String igRoot) {
139+
this.igRoot = igRoot;
140+
}
141+
142+
public FhirContext getFhirContext() {
143+
return fhirContext;
144+
}
145+
146+
public void setFhirContext(FhirContext fhirContext) {
147+
this.fhirContext = fhirContext;
148+
}
149+
150+
public boolean isIncludeDependencies() {
151+
return includeDependencies;
152+
}
153+
154+
public void setIncludeDependencies(boolean includeDependencies) {
155+
this.includeDependencies = includeDependencies;
156+
}
157+
158+
public boolean isIncludeTerminology() {
159+
return includeTerminology;
160+
}
161+
162+
public void setIncludeTerminology(boolean includeTerminology) {
163+
this.includeTerminology = includeTerminology;
164+
}
165+
166+
public boolean isIncludeTests() {
167+
return includeTests;
168+
}
169+
170+
public void setIncludeTests(boolean includeTests) {
171+
this.includeTests = includeTests;
172+
}
173+
174+
public String getFhirServerUrl() {
175+
return fhirServerUrl;
176+
}
177+
178+
public void setFhirServerUrl(String fhirServerUrl) {
179+
this.fhirServerUrl = fhirServerUrl;
180+
}
181+
182+
public IGenericClient getFhirClient() {
183+
return fhirClient;
184+
}
185+
186+
public void setFhirClient(IGenericClient fhirClient) {
187+
this.fhirClient = fhirClient;
188+
}
189+
190+
public String getBundleOutputPath() {
191+
return bundleOutputPath;
192+
}
193+
194+
public void setBundleOutputPath(String bundleOutputPath) {
195+
this.bundleOutputPath = bundleOutputPath;
196+
}
197+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.opencds.cqf.tooling.packaging;
2+
3+
import ca.uhn.fhir.context.FhirContext;
4+
import ca.uhn.fhir.context.FhirVersionEnum;
5+
import org.opencds.cqf.tooling.packaging.r4.PackageMeasure;
6+
import org.opencds.cqf.tooling.utilities.IOUtils;
7+
8+
public class PackageMeasures {
9+
10+
public PackageMeasures(String igRoot, FhirContext fhirContext, boolean includeDependencies, boolean includeTerminology, boolean includeTests, String fhirServerUrl) {
11+
// This is expected to be called during refresh - safe to assume the Measure paths will be present
12+
var measureResourcePaths = IOUtils.getMeasurePaths(fhirContext);
13+
if (fhirContext.getVersion().getVersion() == FhirVersionEnum.R4) {
14+
measureResourcePaths.forEach(
15+
path -> new PackageMeasure(igRoot, fhirContext, path, includeDependencies, includeTerminology, includeTests, fhirServerUrl)
16+
.packageArtifact());
17+
} else {
18+
throw new UnsupportedOperationException("Package operation for Measure resources is not supported for FHIR version: " + fhirContext.getVersion().getVersion().getFhirVersionString());
19+
}
20+
}
21+
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.opencds.cqf.tooling.packaging;
2+
3+
import ca.uhn.fhir.context.FhirContext;
4+
import ca.uhn.fhir.context.FhirVersionEnum;
5+
import org.opencds.cqf.tooling.packaging.r4.PackagePlanDefinition;
6+
import org.opencds.cqf.tooling.utilities.IOUtils;
7+
8+
public class PackagePlanDefinitions {
9+
10+
public PackagePlanDefinitions(String igRoot, FhirContext fhirContext, boolean includeDependencies, boolean includeTerminology, boolean includeTests, String fhirServerUrl) {
11+
// This is expected to be called during refresh - safe to assume the PlanDefinition paths will be present
12+
var pdResourcePaths = IOUtils.getPlanDefinitionPaths(fhirContext);
13+
if (fhirContext.getVersion().getVersion() == FhirVersionEnum.R4) {
14+
pdResourcePaths.forEach(
15+
path -> new PackagePlanDefinition(igRoot, fhirContext, path, includeDependencies, includeTerminology, includeTests, fhirServerUrl)
16+
.packageArtifact());
17+
} else {
18+
throw new UnsupportedOperationException("Package operation for PlanDefinition resources is not supported for FHIR version: " + fhirContext.getVersion().getVersion().getFhirVersionString());
19+
}
20+
}
21+
22+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.opencds.cqf.tooling.packaging;
2+
3+
import org.hl7.fhir.instance.model.api.IBaseBundle;
4+
import org.hl7.fhir.instance.model.api.IBaseResource;
5+
6+
import java.util.Set;
7+
8+
public class TestPackage<G extends IBaseResource, B extends IBaseBundle> {
9+
G group;
10+
Set<B> tests;
11+
12+
public G getGroup() {
13+
return group;
14+
}
15+
16+
public void setGroup(G group) {
17+
this.group = group;
18+
}
19+
20+
public Set<B> getTests() {
21+
return tests;
22+
}
23+
24+
public void setTests(Set<B> tests) {
25+
this.tests = tests;
26+
}
27+
}

0 commit comments

Comments
 (0)