hapifhir / hapi-fhir

🔥 HAPI FHIR - Java API for HL7 FHIR Clients and Servers
http://hapifhir.io
Apache License 2.0
2k stars 1.31k forks source link

NullPointerException when using PrePopulatedValidationSupport #2394

Open holger-stenzhorn opened 3 years ago

holger-stenzhorn commented 3 years ago

Describe the bug

When trying to validate resources against profiles, value sets and code lists contained in the modules of https://simplifier.net/organization/koordinationsstellemii I receive ca.uhn.fhir.rest.server.exceptions.InternalErrorException: Failed to generate snapshot caused by java.lang.NullPointerException: Cannot invoke "ca.uhn.fhir.context.support.IValidationSupport$ValueSetExpansionOutcome.getValueSet()" because "expanded" is null.

To Reproduce To demonstratre the behavior I have created a minimal test class with some minimal profiles and value sets.

If you run the following class...

package de.beispiel.fhir;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;

import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;

import org.hl7.fhir.r4.model.ValueSet;

public class Test {

  public static void main(String[] args) {
    var structureDefinition1 = """
        {
          "resourceType": "StructureDefinition",
          "url": "structureDefinition1",
          "type": "Condition",
          "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Condition",
          "derivation": "constraint",
          "differential": {
            "element": [
              {
                "path": "Condition.code.coding",
                "binding": {
                  "strength": "required",
                  "valueSet": "valueSet1"
                }
              }
            ]
          }
        }
        """;

    var structureDefinition2 = """
        {
          "resourceType": "StructureDefinition",
          "url": "structureDefinition2",
          "type": "Condition",
          "baseDefinition": "structureDefinition1",
          "derivation": "constraint",
          "differential": {
            "element": [
              {
                "path": "Condition.code.coding",
                "binding": {
                  "strength": "required",
                  "valueSet": "valueSet2"
                }
              }
            ]
          }
        }
        """;

    var valueSet1 = """
        {
          "resourceType": "ValueSet",
          "url": "valueSet1"
        }
        """;

    var valueSet2 = """
        {
          "resourceType": "ValueSet",
          "url": "valueSet2"
        }
        """;

    var condition = """
        {
        "resourceType": "Condition",
        "meta": {
            "profile": [ "structureDefinition2" ]
          }
        }
        """;

    var fhirContext = FhirContext.forR4();
    var jsonParser = fhirContext.newJsonParser();

    var prePopulatedValidationSupport = new PrePopulatedValidationSupport(fhirContext);
    prePopulatedValidationSupport.addStructureDefinition(jsonParser.parseResource(structureDefinition1));
    prePopulatedValidationSupport.addStructureDefinition(jsonParser.parseResource(structureDefinition2));;
    prePopulatedValidationSupport.addValueSet((ValueSet) jsonParser.parseResource(valueSet1));
    prePopulatedValidationSupport.addValueSet((ValueSet) jsonParser.parseResource(valueSet2));

    var validationSupportChain = new ValidationSupportChain(
        new DefaultProfileValidationSupport(fhirContext),
        new SnapshotGeneratingValidationSupport(fhirContext),
        prePopulatedValidationSupport);

    var fhirInstanceValidator = new FhirInstanceValidator(validationSupportChain);

    fhirContext.newValidator().registerValidatorModule(fhirInstanceValidator).validateWithResult(condition);
  }

}

...then one receives the following stack trace...

Exception in thread "main" ca.uhn.fhir.rest.server.exceptions.InternalErrorException: Failed to generate snapshot
    at org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport.generateSnapshot(SnapshotGeneratingValidationSupport.java:134)
    at org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain.generateSnapshot(ValidationSupportChain.java:66)
    at org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper.lambda$new$0(VersionSpecificWorkerContextWrapper.java:97)
    at com.github.benmanes.caffeine.cache.LocalLoadingCache.lambda$newMappingFunction$2(LocalLoadingCache.java:141)
    at com.github.benmanes.caffeine.cache.BoundedLocalCache.lambda$doComputeIfAbsent$14(BoundedLocalCache.java:2346)
    at java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1916)
    at com.github.benmanes.caffeine.cache.BoundedLocalCache.doComputeIfAbsent(BoundedLocalCache.java:2344)
    at com.github.benmanes.caffeine.cache.BoundedLocalCache.computeIfAbsent(BoundedLocalCache.java:2327)
    at com.github.benmanes.caffeine.cache.LocalCache.computeIfAbsent(LocalCache.java:108)
    at com.github.benmanes.caffeine.cache.LocalLoadingCache.get(LocalLoadingCache.java:54)
    at org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper.fetchResource(VersionSpecificWorkerContextWrapper.java:322)
    at org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper.fetchRawProfile(VersionSpecificWorkerContextWrapper.java:398)
    at org.hl7.fhir.common.hapi.validation.validator.ValidatorWrapper.fetchAndAddProfile(ValidatorWrapper.java:195)
    at org.hl7.fhir.common.hapi.validation.validator.ValidatorWrapper.validate(ValidatorWrapper.java:156)
    at org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator.validate(FhirInstanceValidator.java:207)
    at org.hl7.fhir.common.hapi.validation.validator.BaseValidatorBridge.doValidate(BaseValidatorBridge.java:22)
    at org.hl7.fhir.common.hapi.validation.validator.BaseValidatorBridge.validateResource(BaseValidatorBridge.java:45)
    at org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator.validateResource(FhirInstanceValidator.java:29)
    at ca.uhn.fhir.validation.FhirValidator.validateWithResult(FhirValidator.java:245)
    at ca.uhn.fhir.validation.FhirValidator.validateWithResult(FhirValidator.java:200)
    at de.beispiel.fhir.Test.main(Test.java:97)
Caused by: java.lang.NullPointerException: Cannot invoke "ca.uhn.fhir.context.support.IValidationSupport$ValueSetExpansionOutcome.getValueSet()" because "expanded" is null
    at org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper.expandVS(VersionSpecificWorkerContextWrapper.java:266)
    at org.hl7.fhir.r5.conformance.ProfileUtilities.updateFromDefinition(ProfileUtilities.java:2812)
    at org.hl7.fhir.r5.conformance.ProfileUtilities.processPaths(ProfileUtilities.java:1134)
    at org.hl7.fhir.r5.conformance.ProfileUtilities.processPaths(ProfileUtilities.java:1025)
    at org.hl7.fhir.r5.conformance.ProfileUtilities.processPaths(ProfileUtilities.java:1025)
    at org.hl7.fhir.r5.conformance.ProfileUtilities.generateSnapshot(ProfileUtilities.java:612)
    at org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport.generateSnapshot(SnapshotGeneratingValidationSupport.java:104)
    ... 20 more

After some debugging I was actually able to fix this issue for me by extending org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport making expandValueSet(...) not return null but new ValueSetExpansionOutcome(theValueSetToExpand):

package de.beispiel.fhir;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;

import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

@SuppressWarnings("unchecked")
public class FixedPrePopulatedValidationSupport extends PrePopulatedValidationSupport {

  public FixedPrePopulatedValidationSupport(FhirContext fhirContext) {
    super(fhirContext);
  }

  @Override
  public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext,
                                                 @Nullable ValueSetExpansionOptions theExpansionOptions,
                                                 @Nonnull IBaseResource theValueSetToExpand) {
    return new ValueSetExpansionOutcome(theValueSetToExpand);
  }

}

Expected behavior The given FHIR resource should validate without any error or excpetion.

Screenshots n/a

Environment (please complete the following information):

Additional context Add any other context about the problem here.

jamesagnew commented 3 years ago

I notice you don't have InMemoryTerminologyServerValidationSupport in your chain. Does the situation improve if you add it?

holger-stenzhorn commented 3 years ago

Yes, you are absolutely right: For the above test class the addition of InMemoryTerminologyServerValidationSupport resolves the issue indeed.

Therefore I have "updated" the test class in such a way that the exception occurs. The NPMs used in it can be found at:

package de.beispiel.fhir;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;

import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;

import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.npm.NpmPackage;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

import static org.apache.commons.lang3.function.Failable.asFunction;

import static org.hl7.fhir.r4.model.codesystems.ResourceTypes.STRUCTUREDEFINITION;
import static org.hl7.fhir.r4.model.codesystems.ResourceTypes.VALUESET;
import static org.hl7.fhir.r4.model.codesystems.ResourceTypes.fromCode;

public class Test {

  public static void main(String[] args) throws Exception {
    var condition = """
        {
          "resourceType": "Procedure",
          "meta": {
            "profile": [ "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/dialysis" ]
          }
        }
        """;

    var profiles = Path.of("/Users/holger/Developer/hapi-test/profiles");

    var fhirContext = FhirContext.forR4();
    var jsonParser = fhirContext.newJsonParser();

    var prePopulatedValidationSupport = new PrePopulatedValidationSupport(fhirContext);
    Stream.of(profiles.resolve("de.gecco-1.0.3.tgz"),
              profiles.resolve("de.medizininformatikinitiative.kerndatensatz.prozedur-1.0.5.tgz"))
         .map(asFunction(Files::newInputStream))
         .map(asFunction(NpmPackage::fromPackage))
         .flatMap(asFunction(npmPackage ->
             npmPackage.listResources(STRUCTUREDEFINITION.toCode(), VALUESET.toCode()).stream()
                 .map(asFunction(npmPackage::loadResource))))
         .map(jsonParser::parseResource)
         .forEach(resource -> {
           switch (fromCode(resource.fhirType())) {
             case STRUCTUREDEFINITION: prePopulatedValidationSupport.addStructureDefinition(resource); break;
             case VALUESET: prePopulatedValidationSupport.addValueSet((ValueSet) resource);
           }
         });

    var validationSupportChain = new ValidationSupportChain(
        new DefaultProfileValidationSupport(fhirContext),
        new InMemoryTerminologyServerValidationSupport(fhirContext),
        new SnapshotGeneratingValidationSupport(fhirContext),
        prePopulatedValidationSupport);

    var fhirInstanceValidator = new FhirInstanceValidator(validationSupportChain);

    fhirContext.newValidator().registerValidatorModule(fhirInstanceValidator).validateWithResult(condition);

  }

}