Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package ca.uhn.fhir.jpa.packages;

import ca.uhn.fhir.implementationguide.ImplementationGuideCreator;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.test.util.LogbackTestExtension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class NpmJpaValidationSupportIT extends BaseJpaR4Test {
@Autowired
private NpmJpaValidationSupport mySvc;

@Autowired
private IPackageInstallerSvc myPackageInstallerSvc;

@RegisterExtension
public final LogbackTestExtension myLogbackTestExtension = new LogbackTestExtension(NpmJpaValidationSupport.class);

@Test
public void loadIGAndFetchSpecificResource(@TempDir Path theTempDirPath) throws IOException {
// create an IG
ImplementationGuideCreator igCreator = new ImplementationGuideCreator(myFhirContext);
igCreator.setDirectory(theTempDirPath);

// create some resources
String spUrl = "http://localhost/ig-test-dir/sp";
String qUrl = "http://localhost/ig-test-dir/q";
for (int i = 0; i < 2; i++) {
SearchParameter sp = new SearchParameter();
sp.setUrl(spUrl + i);
sp.setCode("helloWorld" + i);
sp.setName(sp.getCode());
sp.setDescription("description");
sp.setType(Enumerations.SearchParamType.STRING);
sp.addBase("Patient");
sp.setExpression("Patient.name.given");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
igCreator.addResourceToIG(sp.getName(), sp);

Questionnaire questionnaire = new Questionnaire();
questionnaire.setUrl(qUrl + i);
questionnaire.setStatus(Enumerations.PublicationStatus.ACTIVE);
questionnaire.setName("questionnaire" + i);
igCreator.addResourceToIG(questionnaire.getName(), questionnaire);

// others?
}

PackageInstallationSpec installationSpec = createAndInstallPackageSpec(igCreator, PackageInstallationSpec.InstallModeEnum.STORE_ONLY);

IBaseResource sp = mySvc.fetchResource("SearchParameter", spUrl + "1");
assertNotNull(sp);
assertTrue(sp instanceof SearchParameter);
assertEquals(spUrl + "1", ((SearchParameter)sp).getUrl());
IBaseResource q = mySvc.fetchResource("Questionnaire", qUrl + "1");
assertNotNull(q);
assertTrue(q instanceof Questionnaire);
assertEquals(qUrl + "1", ((Questionnaire)q).getUrl());
}

private PackageInstallationSpec createAndInstallPackageSpec(ImplementationGuideCreator theIgCreator,
PackageInstallationSpec.InstallModeEnum theInstallModeEnum) throws IOException {
// create a source directory
Path outputFileName = theIgCreator.createTestIG();

// add some NPM package
PackageInstallationSpec spec = new PackageInstallationSpec()
.setName(theIgCreator.getPackageName())
.setVersion(theIgCreator.getPackageVersion())
.setInstallMode(theInstallModeEnum)
.setPackageContents(Files.readAllBytes(outputFileName))
;
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);

assertNotNull(outcome);
assertTrue(outcome.getMessage()
.stream().anyMatch(m -> m.contains("Successfully added package")),
String.join(", ", outcome.getMessage()));
return spec;
}
}
7 changes: 7 additions & 0 deletions hapi-fhir-test-utilities/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<!-- Apache compres: https://commons.apache.org/proper/commons-compress/ -->
<!-- Apache license -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.28.0</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package ca.uhn.fhir.implementationguide;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;

public class GZipCreatorUtil {

/**
* Create a tarball of the provided input and saves it to the provided output.
*
* @param theSource - the input path to the directory containing all source files to be zipped
* @param theOutput - the output path to the gzip file.
*/
public static void createTarGz(Path theSource, Path theOutput) throws IOException {
try (OutputStream fos = Files.newOutputStream(theOutput);
BufferedOutputStream bos = new BufferedOutputStream(fos);
GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(bos);
TarArchiveOutputStream taos = new TarArchiveOutputStream(gzos)) {
addFilesToTarGz(theSource, "", taos);
}
}

private static void addFilesToTarGz(Path thePath, String theParent, TarArchiveOutputStream theTarballOutputStream)
throws IOException {
String entryName = theParent + thePath.getFileName().toString();
TarArchiveEntry entry = new TarArchiveEntry(thePath.toFile(), entryName);
theTarballOutputStream.putArchiveEntry(entry);

if (Files.isRegularFile(thePath)) {
// add file
try (InputStream fis = Files.newInputStream(thePath)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
theTarballOutputStream.write(buffer, 0, len);
}
}
theTarballOutputStream.closeArchiveEntry();
} else {
theTarballOutputStream.closeArchiveEntry();
// walk directory
try (DirectoryStream<Path> stream = Files.newDirectoryStream(thePath)) {
for (Path child : stream) {
addFilesToTarGz(child, entryName + "/", theTarballOutputStream);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package ca.uhn.fhir.implementationguide;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.util.FhirTerser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import jakarta.annotation.Nonnull;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.intellij.lang.annotations.Language;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ImplementationGuideCreator {

private static final Logger ourLog = LoggerFactory.getLogger(ImplementationGuideCreator.class);

@Language("JSON")
private static final String PACKAGE_JSON_BASE =
"""
{
"name": "test.fhir.ca.com",
"version": "1.2.3",
"tools-version": 3,
"type": "fhir.ig",
"date": "20200831134427",
"license": "not-open-source",
"canonical": "http://test-ig.com/fhir/us/providerdataexchange",
"url": "file://C:\\\\dev\\\\test-exchange\\\\fsh\\\\build\\\\output",
"title": "Test Implementation Guide",
"description": "Test Implementation Guide",
"fhirVersions": [
"4.0.1"
],
"dependencies": {
},
"author": "SmileCDR",
"maintainers": [
{
"name": "Smile",
"email": "[email protected]",
"url": "https://www.smilecdr.com"
}
],
"directories": {
"lib": "package",
"example": "example"
}
}
""";

private Path myDir;
private final FhirContext myFhirContext;

private final FhirTerser myTerser;
private final IParser myParser;

private final String myPackageJson;

private final String myPackageName;
private final String myPackageVersion;

private final Map<String, IBaseResource> myResourcesToInclude = new HashMap<>();

public ImplementationGuideCreator(@Nonnull FhirContext theFhirContext) throws JsonProcessingException {
this(theFhirContext, "test.fhir.ca.com", "1.2.3");
}

public ImplementationGuideCreator(
@Nonnull FhirContext theFhirContext, String thePackageName, String thePackageVersion)
throws JsonProcessingException {
this(
theFhirContext,
theFhirContext.getVersion().getVersion().getFhirVersionString(),
thePackageName,
thePackageVersion);
}

/**
* Constructor
* @param theFhirContext - FhirContext to use
* @param theFhirVersion - fhir version to use (provided to allow setting a custom value different form the FhirContext)
* @param theName - name to set in package.json's name field
* @param theVersion - version to set in package.json's version field
*/
@SuppressWarnings("unchecked")
public ImplementationGuideCreator(
@Nonnull FhirContext theFhirContext, String theFhirVersion, String theName, String theVersion)
throws JsonProcessingException {
myFhirContext = theFhirContext;
myTerser = myFhirContext.newTerser();
myParser = myFhirContext.newJsonParser();
myPackageName = theName;
myPackageVersion = theVersion;

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);

Map<String, Object> mapJson = mapper.readValue(PACKAGE_JSON_BASE, Map.class);

// update provided values
List<String> versions = (List<String>) mapJson.get("fhirVersions");
versions.clear();
versions.add(theFhirVersion);
mapJson.replace("name", myPackageName);
mapJson.replace("version", myPackageVersion);

myPackageJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapJson);

ourLog.info(myPackageJson);
}

/**
* Sets the directory where files will be created.
* Should be a temp dir for tests.
*/
public ImplementationGuideCreator setDirectory(Path thePath) {
myDir = thePath;
return this;
}

public String getPackageName() {
return myPackageName;
}

public String getPackageVersion() {
return myPackageVersion;
}

/**
* Adds a Resource to the ImplementationGuide.
* No validation is done
*/
public void addResourceToIG(String theFileName, IBaseResource theResource) {
myResourcesToInclude.put(theFileName, theResource);
}

private void verifyDir() {
String msg = "Directory must be set first.";

assertNotNull(myDir, msg);
assertTrue(isNotBlank(myDir.toString()), msg);
}

/**
* Creates an the IG from all the provided SearchParameters,
* zips them up, and provides the path to the newly created gzip file.
*/
public Path createTestIG() throws IOException {
verifyDir();

Path sourceDir = Files.createDirectory(Path.of(myDir.toString(), "package"));

// add the package.json
addFileToDir(myPackageJson, "package.json", sourceDir);

// add search parameters
int index = 0;
for (Map.Entry<String, IBaseResource> nameAndResource : myResourcesToInclude.entrySet()) {
addFileToDir(
myParser.encodeResourceToString(nameAndResource.getValue()),
nameAndResource.getKey() + ".json",
sourceDir);
index++;
}

// we can add other resources here (not req'd for now)

Path outputFileName = Files.createFile(Path.of(myDir.toString(), myPackageName + ".gz.tar"));
GZipCreatorUtil.createTarGz(sourceDir, outputFileName);
return outputFileName;
}

private void addFileToDir(String theContent, String theFileName, Path theOutputPath) throws IOException {
byte[] bytes = new byte[1024];
int length = 0;

try (FileOutputStream outputStream = new FileOutputStream(theOutputPath.toString() + "/" + theFileName)) {
try (InputStream stream = new ByteArrayInputStream(theContent.getBytes(StandardCharsets.UTF_8))) {
while ((length = stream.read(bytes)) >= 0) {
outputStream.write(bytes, 0, length);
}
}
}
}
}
Loading