Skip to content

Commit

Permalink
Using NPM packages load into the validation pipeline instead of indiv…
Browse files Browse the repository at this point in the history
…idual profiles and terminology resources.
  • Loading branch information
seanmcilvenna committed Feb 14, 2024
1 parent e46fe85 commit 5b0718e
Show file tree
Hide file tree
Showing 334 changed files with 108 additions and 79,707 deletions.
3 changes: 1 addition & 2 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.lantanagroup.link.FhirContextProvider;
import com.lantanagroup.link.FhirHelper;
import com.lantanagroup.link.config.api.ApiConfig;
import com.lantanagroup.link.db.SharedService;
import com.lantanagroup.link.validation.Validator;
import org.springframework.beans.factory.InitializingBean;
Expand Down Expand Up @@ -73,8 +74,8 @@ public ApiInit apiInit() {
}

@Bean
public Validator validator(SharedService sharedService) {
return new Validator(sharedService);
public Validator validator(SharedService sharedService, ApiConfig apiConfig) {
return new Validator(sharedService, apiConfig);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,9 @@ public class ApiConfig {
private boolean allowQaEndpoints = false;

private List<ApiInfoGroup> infoGroups = new ArrayList<>();

/**
* <strong>api.validation-packages-path</strong><br>The path to the validation packages
*/
private String validationPackagesPath = "classpath:/packages/**";
}
86 changes: 73 additions & 13 deletions core/src/main/java/com/lantanagroup/link/validation/Validator.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.lantanagroup.link.validation;

import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.validation.*;
import com.lantanagroup.link.Constants;
import com.lantanagroup.link.FhirContextProvider;
import com.lantanagroup.link.config.api.ApiConfig;
import com.lantanagroup.link.db.SharedService;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -15,18 +18,20 @@
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class Validator {
protected static final Logger logger = LoggerFactory.getLogger(Validator.class);
Expand All @@ -41,8 +46,13 @@ public class Validator {
@Setter
private SharedService sharedService;

public Validator(SharedService sharedService) {
private final List<String> allowedPackagePrefixes = List.of("structuredefinition-", "valueset-", "codesystem-", "implementationguide-", "measure-", "library-");
@Setter
private ApiConfig apiConfig;

public Validator(SharedService sharedService, ApiConfig apiConfig) {
this.sharedService = sharedService;
this.apiConfig = apiConfig;
this.prePopulatedValidationSupport = new PrePopulatedValidationSupport(FhirContextProvider.getFhirContext());
}

Expand Down Expand Up @@ -93,24 +103,74 @@ private static OperationOutcome.IssueType getIssueCode(String messageId) {
}
}

private void writeConformanceResourcesToFile() {

This comment has been minimized.

Copy link
@smailliwcs

smailliwcs Feb 15, 2024

Contributor

Is this just a debugging thing?

This comment has been minimized.

Copy link
@seanmcilvenna

seanmcilvenna Feb 15, 2024

Author Contributor

Yes, it is. It could probably be removed. But, I found it useful.

This comment has been minimized.

Copy link
@seanmcilvenna

seanmcilvenna Feb 15, 2024

Author Contributor

And I realized I didn't comment out the call to it, like I thought I did. I just commented out the call to writeConformanceResourcesToFile()... Commit inbound.

String resources = this.prePopulatedValidationSupport.fetchAllConformanceResources().stream()
.map(Object::toString)
.collect(Collectors.joining("\n"));
try {
Files.writeString(Path.of("d:\\code\\link\\conformance-resources.txt"), resources);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void init() {
this.loadFiles();
this.loadPackages();
this.writeConformanceResourcesToFile();

this.updateMeasures();

this.validator = FhirContextProvider.getFhirContext().newValidator();
this.validator.setExecutorService(Executors.newWorkStealingPool());
IValidatorModule module = new FhirInstanceValidator(this.getCachingValidationSupport());
IValidatorModule module = new FhirInstanceValidator(this.getValidationSupportChain());
this.validator.registerValidatorModule(module);
this.validator.setConcurrentBundleValidation(true);
}

private void loadFiles() {
private void loadPackages() {
if (StringUtils.isEmpty(this.apiConfig.getValidationPackagesPath())) {
logger.info("No validation packages path configured");
return;
}

try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
this.loadClassResources(resolver.getResources("terminology/**"));

This comment has been minimized.

Copy link
@smailliwcs

smailliwcs Feb 15, 2024

Contributor

Looks like there's still one artifact hanging around in the terminology directory. Should have been deleted by this commit?

This comment has been minimized.

Copy link
@seanmcilvenna

seanmcilvenna Feb 15, 2024

Author Contributor

No. I intentionally left it there. I did a side-by-side comparison of the resources that were in the prePopulatedValidationSupport instance with before my changes and after. I found that this one resource doesn't exist in any of the packages, for whatever reason.

This comment has been minimized.

Copy link
@smailliwcs

smailliwcs Feb 15, 2024

Contributor

Interesting ... we still need to explicitly load it, even though it's in R4 core?

https://simplifier.net/packages/hl7.fhir.r4.core/4.0.1/files/82054

this.loadClassResources(resolver.getResources("profiles/**"));
} catch (IOException ex) {
logger.error("Error loading resources for validation", ex);
org.springframework.core.io.Resource[] resources = resolver.getResources(this.apiConfig.getValidationPackagesPath());
for (org.springframework.core.io.Resource packageResource : resources) {
if (packageResource.getFile().isDirectory()) {
continue;
} else if (!packageResource.getFile().getName().endsWith(".tgz")) {
logger.warn("Unexpected package file name {}", packageResource.getFilename());
continue;
}

logger.info("Loading package {}", packageResource.getFilename());
NpmPackage npmPackage = NpmPackage.fromPackage(packageResource.getInputStream());

This comment has been minimized.

Copy link
@smailliwcs

smailliwcs Feb 15, 2024

Contributor

Once you have an NpmPackage, you should be able to do something like this (rather than manually wrangling folders/files):

IParser parser = fhirContext.newJsonParser();
for (String fileName : npmPackage.listResources(RESOURCE_TYPES)) {
    logger.debug("Reading resource: {}", fileName);
    IBaseResource resource;
    try (InputStream stream = npmPackage.load(fileName)) {
        resource = parser.parseResource(stream);
    }
    logger.debug("Adding resource: {}", resource.getIdElement());
    prePopulatedValidationSupport.addResource(resource);
}

Where RESOURCE_TYPES is a String[] array of the desired resource types. (Or you could list them out one by one, since the listResources parameter is a varargs String....)

This comment has been minimized.

Copy link
@seanmcilvenna

seanmcilvenna Feb 15, 2024

Author Contributor

Good call. Updated.


if (npmPackage.getFolders().containsKey("package")) {
NpmPackage.NpmPackageFolder packageFolder = npmPackage.getFolders().get("package");
Iterator var3 = packageFolder.listFiles().iterator();

while (var3.hasNext()) {
String nextFile = (String) var3.next();
String lowerCaseFileName = nextFile.toLowerCase(Locale.US);
String lowerCaseFileNamePrefix = lowerCaseFileName.substring(0, lowerCaseFileName.indexOf("-") + 1);

if (allowedPackagePrefixes.contains(lowerCaseFileNamePrefix) && lowerCaseFileName.endsWith(".json")) {
String input = new String(packageFolder.getContent().get(nextFile), StandardCharsets.UTF_8);
this.jsonParser.setParserErrorHandler(new LenientErrorHandler(false));
try {
IBaseResource resource = this.jsonParser.parseResource(input);
this.prePopulatedValidationSupport.addResource(resource);
} catch (DataFormatException ex) {
logger.error("Error parsing package {} resource {}", packageResource, nextFile, ex);
}
}
}
}
}
} catch (IOException e) {
logger.error("Error loading packages for validation: {}", e.getMessage());
}
}

Expand Down Expand Up @@ -161,7 +221,7 @@ private void loadClassResources(org.springframework.core.io.Resource[] classReso
}
}

private CachingValidationSupport getCachingValidationSupport() {
private CachingValidationSupport getValidationSupportChain() {
ValidationSupportChain validationSupportChain = new ValidationSupportChain(
new DefaultProfileValidationSupport(FhirContextProvider.getFhirContext()),
new InMemoryTerminologyServerValidationSupport(FhirContextProvider.getFhirContext()),
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 5b0718e

Please sign in to comment.