Skip to content

Commit

Permalink
Merge pull request #698 from lantanagroup/LNK-2065
Browse files Browse the repository at this point in the history
LNK-2065: Using NPM packages to load resources for validation
  • Loading branch information
smailliwcs committed Feb 16, 2024
2 parents 0aa9503 + 3bb4d14 commit 1dd4ad5
Show file tree
Hide file tree
Showing 334 changed files with 115 additions and 79,709 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 @@ -182,4 +182,9 @@ public class ApiConfig {

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

/**
* <strong>api.validation-packages-path</strong><br>The path to the validation packages
*/
private String validationPackagesPath = "classpath:/packages/**";

}
95 changes: 80 additions & 15 deletions core/src/main/java/com/lantanagroup/link/validation/Validator.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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.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 +17,23 @@
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.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
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 +48,14 @@ public class Validator {
@Setter
private SharedService sharedService;

public Validator(SharedService sharedService) {
private final List<String> allowsResourceTypes = 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,32 +106,84 @@ private static OperationOutcome.IssueType getIssueCode(String messageId) {
}
}

/**
* Writes all conformance resources to a file for debugging purposes
*/
private void writeConformanceResourcesToFile() {
String resources = this.prePopulatedValidationSupport.fetchAllConformanceResources().stream()
.map(Object::toString)
.collect(Collectors.joining("\n"));
try {
Files.writeString(Path.of("conformance-resources.txt"), resources);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void init() {
this.loadFiles();
this.updateMeasures();
this.loadPackages();
this.loadTerminology();

// When needed for debugging
//this.writeConformanceResourcesToFile();

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() {
/**
* Load allowed resources for all packages from the configured path into the pre-populated validation support
*/
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.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());

npmPackage.listResources(allowsResourceTypes).forEach(resource -> {
try {
String resourceJson = new String(npmPackage.getFolders().get("package").getContent().get(resource), StandardCharsets.UTF_8);
IBaseResource baseResource = this.jsonParser.parseResource(resourceJson);
this.prePopulatedValidationSupport.addResource(baseResource);
} catch (DataFormatException ex) {
logger.error("Error parsing package {} resource {}", packageResource, resource, ex);
}
});
}
} catch (IOException e) {
logger.error("Error loading packages for validation: {}", e.getMessage());
}
}

public void updateMeasures() {
// Add each measure canonical url to the resource fetcher
this.sharedService.getMeasureDefinitions().forEach(md -> {
// TODO
});
/**
* Load resources from the terminology classpath directory into the pre-populated validation support
*/
private void loadTerminology() {
try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
org.springframework.core.io.Resource[] resources = resolver.getResources("terminology/**");
this.loadClassResources(resources);
} catch (IOException e) {
logger.error("Error loading class resources for validation: {}", e.getMessage());
}

}

private void loadClassResources(org.springframework.core.io.Resource[] classResources) {
Expand Down Expand Up @@ -161,7 +226,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 1dd4ad5

Please sign in to comment.