Skip to content

Commit cbec00e

Browse files
jkiddotadgh
andauthored
Feat/additional folder options (#6802)
* Added reusable feature set from hapifhir/hapi-fhir-jpaserver-starter#784 * Added a few sanity tests * Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallationSpec.java Co-authored-by: tadgh <[email protected]> * Addressed most comments * Reduced package size and added exception * Added changelog --------- Co-authored-by: tadgh <[email protected]>
1 parent 83b7f46 commit cbec00e

File tree

5 files changed

+175
-1
lines changed

5 files changed

+175
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
type: add
3+
issue: 6861
4+
title: "Added support for parsing additional folder resources in IG packages as utility function.
5+
This allows an easier way API-wise of adding e.g. test resources."
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package ca.uhn.fhir.jpa.packages;
2+
3+
import ca.uhn.fhir.context.FhirContext;
4+
import ca.uhn.fhir.parser.IParser;
5+
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
6+
import ca.uhn.fhir.util.BundleBuilder;
7+
import org.hl7.fhir.instance.model.api.IBaseBundle;
8+
import org.hl7.fhir.instance.model.api.IBaseResource;
9+
import org.hl7.fhir.utilities.npm.NpmPackage;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
import java.io.ByteArrayInputStream;
13+
import java.io.IOException;
14+
import java.util.stream.Collectors;
15+
import java.util.*;
16+
17+
public class AdditionalResourcesParser {
18+
19+
public static IBaseBundle bundleAdditionalResources(
20+
Set<String> additionalResources, PackageInstallationSpec packageInstallationSpec, FhirContext fhirContext) {
21+
NpmPackage npmPackage;
22+
try {
23+
npmPackage = NpmPackage.fromPackage(new ByteArrayInputStream(packageInstallationSpec.getPackageContents()));
24+
} catch (IOException e) {
25+
throw new InternalErrorException(e);
26+
}
27+
List<IBaseResource> resources = getAdditionalResources(additionalResources, npmPackage, fhirContext);
28+
29+
BundleBuilder bundleBuilder = new BundleBuilder(fhirContext);
30+
resources.forEach(bundleBuilder::addTransactionUpdateEntry);
31+
return bundleBuilder.getBundle();
32+
}
33+
34+
@NotNull
35+
public static List<IBaseResource> getAdditionalResources(
36+
Set<String> folderNames, NpmPackage npmPackage, FhirContext fhirContext) {
37+
38+
List<NpmPackage.NpmPackageFolder> npmFolders = folderNames.stream()
39+
.map(name -> npmPackage.getFolders().get(name))
40+
.filter(Objects::nonNull)
41+
.collect(Collectors.toList());
42+
43+
List<IBaseResource> resources = new LinkedList<>();
44+
IParser parser = fhirContext.newJsonParser().setSuppressNarratives(true);
45+
46+
for (NpmPackage.NpmPackageFolder folder : npmFolders) {
47+
List<String> fileNames;
48+
try {
49+
fileNames = folder.getTypes().values().stream()
50+
.flatMap(Collection::stream)
51+
.collect(Collectors.toList());
52+
} catch (IOException e) {
53+
throw new InternalErrorException(e.getMessage(), e);
54+
}
55+
56+
resources.addAll(fileNames.stream()
57+
.map(fileName -> {
58+
try {
59+
return new String(folder.fetchFile(fileName));
60+
} catch (IOException e) {
61+
throw new InternalErrorException(e.getMessage(), e);
62+
}
63+
})
64+
.map(parser::parseResource)
65+
.collect(Collectors.toList()));
66+
}
67+
return resources;
68+
}
69+
}

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallationSpec.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import java.util.ArrayList;
3131
import java.util.List;
32+
import java.util.Set;
3233
import java.util.function.Supplier;
3334

3435
@Schema(name = "PackageInstallationSpec", description = "Defines a set of instructions for package installation")
@@ -39,7 +40,8 @@
3940
"installMode",
4041
"installResourceTypes",
4142
"validationMode",
42-
"reloadExisting"
43+
"reloadExisting",
44+
"additionalResourceFolders"
4345
})
4446
@ExampleSupplier({PackageInstallationSpec.ExampleSupplier.class, PackageInstallationSpec.ExampleSupplier2.class})
4547
@JsonInclude(JsonInclude.Include.NON_NULL)
@@ -90,6 +92,12 @@ public class PackageInstallationSpec {
9092
"Any values provided here will be interpreted as a regex. Dependencies with an ID matching any regex will be skipped.")
9193
private List<String> myDependencyExcludes;
9294

95+
@Schema(
96+
description =
97+
"List of folders in the package to extract additional resources from. Each folder listed will be scanned for resources which will be installed as part of package installation.")
98+
@JsonProperty("additionalResourceFolders")
99+
private Set<String> myAdditionalResourceFolders;
100+
93101
@JsonIgnore
94102
private byte[] myPackageContents;
95103

@@ -177,6 +185,14 @@ public void setReloadExisting(boolean reloadExisting) {
177185
this.myReloadExisting = reloadExisting;
178186
}
179187

188+
public Set<String> getAdditionalResourceFolders() {
189+
return myAdditionalResourceFolders;
190+
}
191+
192+
public void setAdditionalResourceFolders(Set<String> additionalResourceFolders) {
193+
this.myAdditionalResourceFolders = additionalResourceFolders;
194+
}
195+
180196
public PackageInstallationSpec addDependencyExclude(String theExclude) {
181197
getDependencyExcludes().add(theExclude);
182198
return this;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package ca.uhn.fhir.jpa.packages;
2+
3+
import ca.uhn.fhir.context.FhirContext;
4+
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
5+
import ca.uhn.fhir.util.ClasspathUtil;
6+
import org.hl7.fhir.r4.model.Bundle;
7+
import org.hl7.fhir.utilities.npm.NpmPackage;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.io.IOException;
11+
import java.util.Set;
12+
13+
import static ca.uhn.fhir.jpa.packages.AdditionalResourcesParser.bundleAdditionalResources;
14+
import static ca.uhn.fhir.jpa.packages.AdditionalResourcesParser.getAdditionalResources;
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertThrows;
18+
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
19+
20+
public class AdditionalResourcesParserTest {
21+
22+
@Test
23+
public void testBundleAdditionalResourcesSingleFolder() throws IOException {
24+
25+
// Arrange
26+
var packageAsBytes = ClasspathUtil.loadResourceAsStream("/cqf-ccc.tgz").readAllBytes();
27+
var packageInstallationSpec = new PackageInstallationSpec().setPackageContents(packageAsBytes).setName("fhir.cqf.ccc").setVersion("0.1.0");
28+
29+
// Act
30+
var bundle = (Bundle) bundleAdditionalResources(Set.of("tests"), packageInstallationSpec, FhirContext.forR4Cached());
31+
32+
// Assert
33+
assertEquals(3, bundle.getEntry().size());
34+
}
35+
36+
@Test
37+
public void testBundleAdditionalResourcesMultipleFolders() throws IOException {
38+
39+
// Arrange
40+
var packageAsBytes = ClasspathUtil.loadResourceAsStream("/cqf-ccc.tgz").readAllBytes();
41+
var packageInstallationSpec = new PackageInstallationSpec().setPackageContents(packageAsBytes).setName("fhir.cqf.ccc").setVersion("0.1.0");
42+
43+
// Act
44+
var bundle = (Bundle) bundleAdditionalResources(Set.of("tests", "package"), packageInstallationSpec, FhirContext.forR4Cached());
45+
46+
// Assert
47+
assertEquals(7, bundle.getEntry().size());
48+
}
49+
50+
@Test
51+
public void testBundleAdditionalResourcesUnknownFolder() throws IOException {
52+
53+
// Arrange
54+
var packageAsBytes = ClasspathUtil.loadResourceAsStream("/cqf-ccc.tgz").readAllBytes();
55+
var packageInstallationSpec = new PackageInstallationSpec().setPackageContents(packageAsBytes).setName("fhir.cqf.ccc").setVersion("0.1.0");
56+
57+
// Act
58+
var bundle = (Bundle) bundleAdditionalResources(Set.of("testsUnknown/", "testsUnknown"), packageInstallationSpec, FhirContext.forR4Cached());
59+
60+
// Assert
61+
assertEquals(0, bundle.getEntry().size());
62+
}
63+
64+
@Test
65+
public void testGetAdditionalResources() throws IOException {
66+
// Arrange
67+
var npmPackage = NpmPackage.fromPackage(ClasspathUtil.loadResourceAsStream("/cqf-ccc.tgz"));
68+
69+
// Act
70+
var resourceList = getAdditionalResources(Set.of("tests"), npmPackage, FhirContext.forR4Cached());
71+
72+
// Assert
73+
assertEquals(3, resourceList.size());
74+
}
75+
76+
@Test
77+
public void testVerifyExceptionHandlingInvalidPackages() {
78+
// Arrange
79+
Exception exception = assertThrows(InternalErrorException.class, () -> NpmPackage.fromPackage(ClasspathUtil.loadResourceAsStream("/invalid.tgz")));
80+
81+
// Assert
82+
assertThat(exception.getMessage()).contains("HAPI-1758: Unable to find classpath resource: /invalid.tgz");
83+
}
84+
}
Binary file not shown.

0 commit comments

Comments
 (0)